diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index b5a7eb75a7..9b988f2b56 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -67,8 +67,6 @@ import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -80,6 +78,7 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
/**
* @author Stian Thorgersen
@@ -270,25 +269,16 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
- protected List getUserSessions(RealmModel realm, Predicate>> predicate, boolean offline) {
+ protected Stream getUserSessionsStream(RealmModel realm, Predicate>> predicate, boolean offline) {
Cache> cache = getCache(offline);
-
cache = CacheDecorators.skipCacheLoaders(cache);
- Stream>> cacheStream = cache.entrySet().stream();
-
- List resultSessions = new LinkedList<>();
-
- Iterator itr = cacheStream.filter(predicate)
+ // return a stream that 'wraps' the infinispan cache stream so that the cache stream's elements are read one by one
+ // and then filtered/mapped locally to avoid serialization issues when trying to manipulate the cache stream directly.
+ return StreamSupport.stream(cache.entrySet().stream().spliterator(), true)
+ .filter(predicate)
.map(Mappers.userSessionEntity())
- .iterator();
-
- while (itr.hasNext()) {
- UserSessionEntity userSessionEntity = itr.next();
- resultSessions.add(wrap(realm, userSessionEntity, offline));
- }
-
- return resultSessions;
+ .map(entity -> this.wrap(realm, entity, offline));
}
@Override
@@ -305,46 +295,45 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
- public List getUserSessions(final RealmModel realm, UserModel user) {
- return getUserSessions(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), false);
+ public Stream getUserSessionsStream(final RealmModel realm, UserModel user) {
+ return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), false);
}
@Override
- public List getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
- return getUserSessions(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), false);
+ public Stream getUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) {
+ return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), false);
}
@Override
public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
- List userSessions = getUserSessions(realm, UserSessionPredicate.create(realm.getId()).brokerSessionId(brokerSessionId), false);
- return userSessions.isEmpty() ? null : userSessions.get(0);
+ return this.getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).brokerSessionId(brokerSessionId), false)
+ .findFirst().orElse(null);
}
@Override
- public List getUserSessions(RealmModel realm, ClientModel client) {
- return getUserSessions(realm, client, -1, -1);
+ public Stream getUserSessionsStream(RealmModel realm, ClientModel client) {
+ return getUserSessionsStream(realm, client, -1, -1);
}
@Override
- public List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
- return getUserSessions(realm, client, firstResult, maxResults, false);
+ public Stream getUserSessionsStream(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+ return getUserSessionsStream(realm, client, firstResult, maxResults, false);
}
- protected List getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
+ protected Stream getUserSessionsStream(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
final String clientUuid = client.getId();
UserSessionPredicate predicate = UserSessionPredicate.create(realm.getId()).client(clientUuid);
return getUserSessionModels(realm, firstResult, maxResults, offline, predicate);
}
- protected List getUserSessionModels(RealmModel realm, int firstResult, int maxResults, boolean offline, UserSessionPredicate predicate) {
+ protected Stream getUserSessionModels(RealmModel realm, int firstResult, int maxResults, boolean offline, UserSessionPredicate predicate) {
Cache> cache = getCache(offline);
cache = CacheDecorators.skipCacheLoaders(cache);
- Cache> clientSessionCache = getClientSessionCache(offline);
- Cache> clientSessionCacheDecorated = CacheDecorators.skipCacheLoaders(clientSessionCache);
-
- Stream stream = cache.entrySet().stream()
+ // return a stream that 'wraps' the infinispan cache stream so that the cache stream's elements are read one by one
+ // and then filtered/mapped locally to avoid serialization issues when trying to manipulate the cache stream directly.
+ Stream stream = StreamSupport.stream(cache.entrySet().stream().spliterator(), true)
.filter(predicate)
.map(Mappers.userSessionEntity())
.sorted(Comparators.userSessionLastSessionRefresh());
@@ -357,16 +346,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
stream = stream.limit(maxResults);
}
- final List sessions = new LinkedList<>();
- Iterator itr = stream.iterator();
-
- while (itr.hasNext()) {
- UserSessionEntity userSessionEntity = itr.next();
- sessions.add(wrap(realm, userSessionEntity, offline));
- }
-
-
- return sessions;
+ return stream.map(entity -> this.wrap(realm, entity, offline));
}
@Override
@@ -839,13 +819,13 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public UserSessionModel getOfflineUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
- List userSessions = getUserSessions(realm, UserSessionPredicate.create(realm.getId()).brokerSessionId(brokerSessionId), true);
- return userSessions.isEmpty() ? null : userSessions.get(0);
+ return this.getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).brokerSessionId(brokerSessionId), true)
+ .findFirst().orElse(null);
}
@Override
- public List getOfflineUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
- return getUserSessions(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), true);
+ public Stream getOfflineUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) {
+ return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), true);
}
@Override
@@ -856,8 +836,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
}
-
-
@Override
public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession) {
UserSessionAdapter userSessionAdapter = (offlineUserSession instanceof UserSessionAdapter) ? (UserSessionAdapter) offlineUserSession :
@@ -874,23 +852,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
- public List getOfflineUserSessions(RealmModel realm, UserModel user) {
- List userSessions = new LinkedList<>();
-
- Cache> cache = CacheDecorators.skipCacheLoaders(offlineSessionCache);
-
- Iterator itr = cache.entrySet().stream()
- .filter(UserSessionPredicate.create(realm.getId()).user(user.getId()))
- .map(Mappers.userSessionEntity())
- .iterator();
-
- while (itr.hasNext()) {
- UserSessionEntity userSessionEntity = itr.next();
- UserSessionModel userSession = wrap(realm, userSessionEntity, true);
- userSessions.add(userSession);
- }
-
- return userSessions;
+ public Stream getOfflineUserSessionsStream(RealmModel realm, UserModel user) {
+ return this.getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), true);
}
@Override
@@ -899,8 +862,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
- public List getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max) {
- return getUserSessions(realm, client, first, max, true);
+ public Stream getOfflineUserSessionsStream(RealmModel realm, ClientModel client, int first, int max) {
+ return getUserSessionsStream(realm, client, first, max, true);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index 538ef76922..0db20f5759 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -24,6 +24,8 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* @author Bill Burke
@@ -40,10 +42,79 @@ public interface UserSessionProvider extends Provider {
String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId, UserSessionModel.SessionPersistenceState persistenceState);
UserSessionModel getUserSession(RealmModel realm, String id);
- List getUserSessions(RealmModel realm, UserModel user);
- List getUserSessions(RealmModel realm, ClientModel client);
- List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults);
- List getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId);
+
+ /**
+ * @deprecated Use {@link #getUserSessionsStream(RealmModel, ClientModel) getUserSessionsStream} instead.
+ */
+ @Deprecated
+ default List getUserSessions(RealmModel realm, UserModel user) {
+ return this.getUserSessionsStream(realm, user).collect(Collectors.toList());
+ }
+
+ /**
+ * Obtains the user sessions associated with the specified user.
+ *
+ * @param realm a reference to the realm.
+ * @param user the user whose sessions are being searched.
+ * @return a non-null {@link Stream} of user sessions.
+ */
+ Stream getUserSessionsStream(RealmModel realm, UserModel user);
+
+ /**
+ * @deprecated Use {@link #getUserSessionsStream(RealmModel, ClientModel) getUserSessionsStream} instead.
+ */
+ @Deprecated
+ default List getUserSessions(RealmModel realm, ClientModel client) {
+ return this.getUserSessionsStream(realm, client).collect(Collectors.toList());
+ }
+
+ /**
+ * Obtains the user sessions associated with the specified client.
+ *
+ * @param realm a reference to the realm.
+ * @param client the client whose user sessions are being searched.
+ * @return a non-null {@link Stream} of user sessions.
+ */
+ Stream getUserSessionsStream(RealmModel realm, ClientModel client);
+
+ /**
+ * @deprecated Use {@link #getUserSessionsStream(RealmModel, ClientModel, int, int) getUserSessionsStream} instead.
+ */
+ @Deprecated
+ default List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+ return this.getUserSessionsStream(realm, client, firstResult, maxResults).collect(Collectors.toList());
+ }
+
+ /**
+ * Obtains the user sessions associated with the specified client, starting from the {@code firstResult} and containing
+ * at most {@code maxResults}.
+ *
+ * @param realm a reference tot he realm.
+ * @param client the client whose user sessions are being searched.
+ * @param firstResult first result to return. Ignored if negative.
+ * @param maxResults maximum number of results to return. Ignored if negative.
+ * @return a non-null {@link Stream} of user sessions.
+ */
+ Stream getUserSessionsStream(RealmModel realm, ClientModel client, int firstResult, int maxResults);
+
+ /**
+ * @deprecated Use {@link #getUserSessionByBrokerUserIdStream(RealmModel, String) getUserSessionByBrokerUserIdStream}
+ * instead.
+ */
+ @Deprecated
+ default List getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
+ return this.getUserSessionByBrokerUserIdStream(realm, brokerUserId).collect(Collectors.toList());
+ }
+
+ /**
+ * Obtains the user sessions associated with the user that matches the specified {@code brokerUserId}.
+ *
+ * @param realm a reference to the realm.
+ * @param brokerUserId the id of the broker user whose sessions are being searched.
+ * @return a non-null {@link Stream} of user sessions.
+ */
+ Stream getUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId);
+
UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId);
/**
@@ -88,12 +159,66 @@ public interface UserSessionProvider extends Provider {
/** Will automatically attach newly created offline client session to the offlineUserSession **/
AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession);
- List getOfflineUserSessions(RealmModel realm, UserModel user);
+
+ /**
+ * @deprecated Use {@link #getOfflineUserSessionsStream(RealmModel, UserModel) getOfflineUserSessionsStream} instead.
+ */
+ @Deprecated
+ default List getOfflineUserSessions(RealmModel realm, UserModel user) {
+ return this.getOfflineUserSessionsStream(realm, user).collect(Collectors.toList());
+ }
+
+ /**
+ * Obtains the offline user sessions associated with the specified user.
+ *
+ * @param realm a reference to the realm.
+ * @param user the user whose offline sessions are being searched.
+ * @return a non-null {@link Stream} of offline user sessions.
+ */
+ Stream getOfflineUserSessionsStream(RealmModel realm, UserModel user);
+
UserSessionModel getOfflineUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId);
- List getOfflineUserSessionByBrokerUserId(RealmModel realm, String brokerUserId);
+
+ /**
+ * @deprecated Use {@link #getOfflineUserSessionByBrokerUserIdStream(RealmModel, String) getOfflineUserSessionByBrokerUserIdStream}
+ * instead.
+ */
+ @Deprecated
+ default List getOfflineUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
+ return this.getOfflineUserSessionByBrokerUserIdStream(realm, brokerUserId).collect(Collectors.toList());
+ }
+
+ /**
+ * Obtains the offline user sessions associated with the user that matches the specified {@code brokerUserId}.
+ *
+ * @param realm a reference to the realm.
+ * @param brokerUserId the id of the broker user whose sessions are being searched.
+ * @return a non-null {@link Stream} of offline user sessions.
+ */
+ Stream getOfflineUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId);
long getOfflineSessionsCount(RealmModel realm, ClientModel client);
- List getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
+
+ /**
+ * @deprecated use {@link #getOfflineUserSessionsStream(RealmModel, ClientModel, int, int) getOfflineUserSessionsStream}
+ * instead.
+ */
+ @Deprecated
+ default List getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max) {
+ return this.getOfflineUserSessionsStream(realm, client, first, max).collect(Collectors.toList());
+ }
+
+ /**
+ * Obtains the offline user sessions associated with the specified client, starting from the {@code firstResult} and
+ * containing at most {@code maxResults}.
+ *
+ * @param realm a reference tot he realm.
+ * @param client the client whose user sessions are being searched.
+ * @param firstResult first result to return. Ignored if negative.
+ * @param maxResults maximum number of results to return. Ignored if negative.
+ * @return a non-null {@link Stream} of offline user sessions.
+ */
+ Stream getOfflineUserSessionsStream(RealmModel realm, ClientModel client, int firstResult, int maxResults);
/** Triggered by persister during pre-load. It imports authenticatedClientSessions too **/
void importUserSessions(Collection persistentUserSessions, boolean offline);
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
index b8133397bb..77ee2ed0d6 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
@@ -47,7 +47,9 @@ import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* @author Bill Burke
@@ -129,12 +131,11 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
if (getId().equals(authSession.getClientNote(Constants.KC_ACTION_EXECUTING))
&& "on".equals(formData.getFirst("logout-sessions")))
{
- List sessions = session.sessions().getUserSessions(realm, user);
- for (UserSessionModel s : sessions) {
- if (!s.getId().equals(authSession.getParentSession().getId())) {
- AuthenticationManager.backchannelLogout(session, realm, s, session.getContext().getUri(), context.getConnection(), context.getHttpRequest().getHttpHeaders(), true);
- }
- }
+ session.sessions().getUserSessionsStream(realm, user)
+ .filter(s -> !Objects.equals(s.getId(), authSession.getParentSession().getId()))
+ .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
+ .forEach(s -> AuthenticationManager.backchannelLogout(session, realm, s, session.getContext().getUri(),
+ context.getConnection(), context.getHttpRequest().getHttpHeaders(), true));
}
try {
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index adf48c5aa8..4c65e74e57 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -83,9 +83,15 @@ import javax.xml.namespace.QName;
import java.io.IOException;
import java.security.Key;
import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import org.keycloak.protocol.saml.SamlPrincipalType;
import org.keycloak.rotation.HardcodedKeyLocator;
@@ -93,14 +99,14 @@ import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.saml.validators.ConditionsValidator;
import org.keycloak.saml.validators.DestinationValidator;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
import java.net.URI;
import java.security.cert.CertificateException;
-import org.w3c.dom.Element;
-import java.util.*;
import javax.ws.rs.core.MultivaluedMap;
import javax.xml.crypto.dsig.XMLSignature;
-import org.w3c.dom.NodeList;
/**
* @author Bill Burke
@@ -296,22 +302,13 @@ public class SAMLEndpoint {
protected Response logoutRequest(LogoutRequestType request, String relayState) {
String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
- List userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
- for (UserSessionModel userSession : userSessions) {
- if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) {
- continue;
- }
-
- for(Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
- request = it.next().beforeProcessingLogoutRequest(request, userSession, null);
- }
-
- try {
- AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, false);
- } catch (Exception e) {
- logger.warn("failed to do backchannel logout for userSession", e);
- }
- }
+ AtomicReference ref = new AtomicReference<>(request);
+ session.sessions().getUserSessionByBrokerUserIdStream(realm, brokerUserId)
+ .filter(userSession -> userSession.getState() != UserSessionModel.State.LOGGING_OUT &&
+ userSession.getState() != UserSessionModel.State.LOGGED_OUT)
+ .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
+ .forEach(processLogout(ref));
+ request = ref.get();
} else {
for (String sessionIndex : request.getSessionIndex()) {
@@ -369,6 +366,19 @@ public class SAMLEndpoint {
}
+ private Consumer processLogout(AtomicReference ref) {
+ return userSession -> {
+ for(Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
+ ref.set(it.next().beforeProcessingLogoutRequest(ref.get(), userSession, null));
+ }
+ try {
+ AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, false);
+ } catch (Exception e) {
+ logger.warn("failed to do backchannel logout for userSession", e);
+ }
+ };
+ }
+
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
String configEntityId = config.getEntityId();
@@ -578,11 +588,6 @@ public class SAMLEndpoint {
}
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers);
}
-
-
-
-
-
}
protected class PostBinding extends Binding {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index 1265496db3..c32dd13a7f 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -65,8 +65,8 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
-import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.models.UserSessionModel.State.LOGGED_OUT;
@@ -356,34 +356,29 @@ public class LogoutEndpoint {
BackchannelLogoutResponse backchannelLogoutResponse = new BackchannelLogoutResponse();
backchannelLogoutResponse.setLocalLogoutSucceeded(true);
identityProviderAliases.forEach(identityProviderAlias -> {
- List userSessions = session.sessions().getUserSessionByBrokerUserId(realm,
- identityProviderAlias + "." + federatedUserId);
if (logoutOfflineSessions) {
logoutOfflineUserSessions(identityProviderAlias + "." + federatedUserId);
}
- for (UserSessionModel userSession : userSessions) {
- BackchannelLogoutResponse userBackchannelLogoutResponse;
- userBackchannelLogoutResponse = logoutUserSession(userSession);
- backchannelLogoutResponse.setLocalLogoutSucceeded(backchannelLogoutResponse.getLocalLogoutSucceeded()
- && userBackchannelLogoutResponse.getLocalLogoutSucceeded());
- userBackchannelLogoutResponse.getClientResponses()
- .forEach(backchannelLogoutResponse::addClientResponses);
- }
+ session.sessions().getUserSessionByBrokerUserIdStream(realm, identityProviderAlias + "." + federatedUserId)
+ .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
+ .forEach(userSession -> {
+ BackchannelLogoutResponse userBackchannelLogoutResponse = this.logoutUserSession(userSession);
+ backchannelLogoutResponse.setLocalLogoutSucceeded(backchannelLogoutResponse.getLocalLogoutSucceeded()
+ && userBackchannelLogoutResponse.getLocalLogoutSucceeded());
+ userBackchannelLogoutResponse.getClientResponses()
+ .forEach(backchannelLogoutResponse::addClientResponses);
+ });
});
return backchannelLogoutResponse;
}
private void logoutOfflineUserSessions(String brokerUserId) {
- List offlineUserSessions =
- session.sessions().getOfflineUserSessionByBrokerUserId(realm, brokerUserId);
-
UserSessionManager userSessionManager = new UserSessionManager(session);
- for (UserSessionModel offlineUserSession : offlineUserSessions) {
- userSessionManager.revokeOfflineUserSession(offlineUserSession);
- }
+ session.sessions().getOfflineUserSessionByBrokerUserIdStream(realm, brokerUserId).collect(Collectors.toList())
+ .forEach(userSessionManager::revokeOfflineUserSession);
}
private BackchannelLogoutResponse logoutUserSession(UserSessionModel userSession) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java
index b0c7754103..33c9818203 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java
@@ -17,7 +17,8 @@
package org.keycloak.protocol.oidc.endpoints;
-import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
@@ -36,7 +37,6 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.headers.SecurityHeadersProvider;
-import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -223,14 +223,11 @@ public class TokenRevocationEndpoint {
if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType())) {
new UserSessionManager(session).revokeOfflineToken(user, client);
}
-
- List userSessions = session.sessions().getUserSessions(realm, user);
- for (UserSessionModel userSession : userSessions) {
- AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
- if (clientSession != null) {
- org.keycloak.protocol.oidc.TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
- }
- }
+ session.sessions().getUserSessionsStream(realm, user)
+ .map(userSession -> userSession.getAuthenticatedClientSessionByClient(client.getId()))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()) // collect to avoid concurrent modification as dettachClientSession removes the user sessions.
+ .forEach(clientSession -> TokenManager.dettachClientSession(session.sessions(), realm, clientSession));
}
private void revokeAccessToken() {
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index f3646f0c0c..1b83ef23b7 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -548,15 +548,15 @@ public class AuthenticationManager {
* @param headers
*/
public static void backchannelLogoutUserFromClient(KeycloakSession session, RealmModel realm, UserModel user, ClientModel client, UriInfo uriInfo, HttpHeaders headers) {
- List userSessions = session.sessions().getUserSessions(realm, user);
- for (UserSessionModel userSession : userSessions) {
- AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
- if (clientSession != null) {
- backchannelLogoutClientSession(session, realm, clientSession, null, uriInfo, headers);
- clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
- org.keycloak.protocol.oidc.TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
- }
- }
+ session.sessions().getUserSessionsStream(realm, user)
+ .map(userSession -> userSession.getAuthenticatedClientSessionByClient(client.getId()))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()) // collect to avoid concurrent modification.
+ .forEach(clientSession -> {
+ backchannelLogoutClientSession(session, realm, clientSession, null, uriInfo, headers);
+ clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
+ TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
+ });
}
public static Response browserLogout(KeycloakSession session,
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index c72cd1f8e1..d16f6a7cdb 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -37,7 +37,6 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -119,38 +118,6 @@ public class ResourceAdminManager {
return result;
}
- public void logoutUser(RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
- keycloakSession.users().setNotBeforeForUser(realm, user, Time.currentTime());
-
- List userSessions = keycloakSession.sessions().getUserSessions(realm, user);
- logoutUserSessions(realm, userSessions);
- }
-
- protected void logoutUserSessions(RealmModel realm, List userSessions) {
- // Map from "app" to clientSessions for this app
- MultivaluedHashMap clientSessions = new MultivaluedHashMap<>();
- for (UserSessionModel userSession : userSessions) {
- putClientSessions(clientSessions, userSession);
- }
-
- logger.debugv("logging out {0} resources ", clientSessions.size());
- //logger.infov("logging out resources: {0}", clientSessions);
-
- for (Map.Entry> entry : clientSessions.entrySet()) {
- if (entry.getValue().size() == 0) {
- continue;
- }
- logoutClientSessions(realm, entry.getValue().get(0).getClient(), entry.getValue());
- }
- }
-
- private void putClientSessions(MultivaluedHashMap clientSessions, UserSessionModel userSession) {
- for (Map.Entry entry : userSession.getAuthenticatedClientSessions().entrySet()) {
- clientSessions.add(entry.getKey(), entry.getValue());
- }
- }
-
-
public Response logoutClientSession(RealmModel realm, ClientModel resource, AuthenticatedClientSessionModel clientSession) {
return logoutClientSessions(realm, resource, Arrays.asList(clientSession));
}
diff --git a/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java b/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
index 653b9c29ac..c8a90be521 100644
--- a/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
@@ -30,10 +30,11 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.services.ServicesLogger;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
@@ -77,43 +78,42 @@ public class UserSessionManager {
}
public Set findClientsWithOfflineToken(RealmModel realm, UserModel user) {
- List userSessions = kcSession.sessions().getOfflineUserSessions(realm, user);
- Set clients = new HashSet<>();
- for (UserSessionModel userSession : userSessions) {
- Set clientIds = userSession.getAuthenticatedClientSessions().keySet();
- for (String clientUUID : clientIds) {
- ClientModel client = realm.getClientById(clientUUID);
- clients.add(client);
- }
- }
- return clients;
+ return kcSession.sessions().getOfflineUserSessionsStream(realm, user)
+ .flatMap(userSession -> userSession.getAuthenticatedClientSessions().keySet().stream())
+ .map(clientUUID -> realm.getClientById(clientUUID))
+ .collect(Collectors.toSet());
}
+ @Deprecated
public List findOfflineSessions(RealmModel realm, UserModel user) {
- return kcSession.sessions().getOfflineUserSessions(realm, user);
+ return this.findOfflineSessionsStream(realm, user).collect(Collectors.toList());
+ }
+
+ public Stream findOfflineSessionsStream(RealmModel realm, UserModel user) {
+ return kcSession.sessions().getOfflineUserSessionsStream(realm, user);
}
public boolean revokeOfflineToken(UserModel user, ClientModel client) {
RealmModel realm = client.getRealm();
- List userSessions = kcSession.sessions().getOfflineUserSessions(realm, user);
- boolean anyRemoved = false;
- for (UserSessionModel userSession : userSessions) {
- AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
- if (clientSession != null) {
- if (logger.isTraceEnabled()) {
- logger.tracef("Removing existing offline token for user '%s' and client '%s' .",
- user.getUsername(), client.getClientId());
- }
+ AtomicBoolean anyRemoved = new AtomicBoolean(false);
+ kcSession.sessions().getOfflineUserSessionsStream(realm, user).collect(Collectors.toList())
+ .forEach(userSession -> {
+ AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
+ if (clientSession != null) {
+ if (logger.isTraceEnabled()) {
+ logger.tracef("Removing existing offline token for user '%s' and client '%s' .",
+ user.getUsername(), client.getClientId());
+ }
- clientSession.detachFromUserSession();
- persister.removeClientSession(userSession.getId(), client.getId(), true);
- checkOfflineUserSessionHasClientSessions(realm, user, userSession);
- anyRemoved = true;
- }
- }
+ clientSession.detachFromUserSession();
+ persister.removeClientSession(userSession.getId(), client.getId(), true);
+ checkOfflineUserSessionHasClientSessions(realm, user, userSession);
+ anyRemoved.set(true);
+ }
+ });
- return anyRemoved;
+ return anyRemoved.get();
}
public void revokeOfflineUserSession(UserSessionModel userSession) {
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
index ec758021b4..f943a7e793 100755
--- a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
@@ -322,7 +322,7 @@ public class AccountFormService extends AbstractSecuredLocalService {
@GET
public Response sessionsPage() {
if (auth != null) {
- account.setSessions(session.sessions().getUserSessions(realm, auth.getUser()));
+ account.setSessions(session.sessions().getUserSessionsStream(realm, auth.getUser()).collect(Collectors.toList()));
}
return forwardToPage("sessions", AccountPages.SESSIONS);
}
@@ -342,7 +342,6 @@ public class AccountFormService extends AbstractSecuredLocalService {
* lastName
* email
*
- * @param formData
* @return
*/
@Path("/")
@@ -427,10 +426,10 @@ public class AccountFormService extends AbstractSecuredLocalService {
// as time on the token will be same like notBefore
session.users().setNotBeforeForUser(realm, user, Time.currentTime() - 1);
- List userSessions = session.sessions().getUserSessions(realm, user);
- for (UserSessionModel userSession : userSessions) {
- AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, true);
- }
+ session.sessions().getUserSessionsStream(realm, user)
+ .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
+ .forEach(userSession -> AuthenticationManager.backchannelLogout(session, realm, userSession,
+ session.getContext().getUri(), clientConnection, headers, true));
UriBuilder builder = Urls.accountBase(session.getContext().getUri().getBaseUri()).path(AccountFormService.class, "sessionsPage");
String referrer = session.getContext().getUri().getQueryParameters().getFirst("referrer");
@@ -495,7 +494,6 @@ public class AccountFormService extends AbstractSecuredLocalService {
* totp - otp generated by authenticator
* totpSecret - totp secret to register
*
- * @param formData
* @return
*/
@Path("totp")
@@ -567,7 +565,6 @@ public class AccountFormService extends AbstractSecuredLocalService {
* password-new
* pasword-confirm
*
- * @param formData
* @return
*/
@Path("password")
@@ -641,12 +638,9 @@ public class AccountFormService extends AbstractSecuredLocalService {
return account.setError(Response.Status.INTERNAL_SERVER_ERROR, ape.getMessage()).createResponse(AccountPages.PASSWORD);
}
- List sessions = session.sessions().getUserSessions(realm, user);
- for (UserSessionModel s : sessions) {
- if (!s.getId().equals(auth.getSession().getId())) {
- AuthenticationManager.backchannelLogout(session, realm, s, session.getContext().getUri(), clientConnection, headers, true);
- }
- }
+ session.sessions().getUserSessionsStream(realm, user).filter(s -> !Objects.equals(s.getId(), auth.getSession().getId()))
+ .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
+ .forEach(s -> AuthenticationManager.backchannelLogout(session, realm, s, session.getContext().getUri(), clientConnection, headers, true));
event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
index 9f1bc330da..a2077c0c59 100755
--- a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
@@ -33,7 +33,6 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.account.ClientRepresentation;
import org.keycloak.representations.account.ConsentRepresentation;
import org.keycloak.representations.account.ConsentScopeRepresentation;
@@ -79,6 +78,7 @@ import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* @author Stian Thorgersen
@@ -401,51 +401,36 @@ public class AccountRestService {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
- public List applications(@QueryParam("name") String name) {
+ public Stream applications(@QueryParam("name") String name) {
checkAccountApiEnabled();
auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_APPLICATIONS);
Set clients = new HashSet<>();
List inUseClients = new LinkedList<>();
- List sessions = session.sessions().getUserSessions(realm, user);
- for(UserSessionModel s : sessions) {
- for (AuthenticatedClientSessionModel a : s.getAuthenticatedClientSessions().values()) {
- ClientModel client = a.getClient();
- clients.add(client);
- inUseClients.add(client.getClientId());
- }
- }
+ clients.addAll(session.sessions().getUserSessionsStream(realm, user)
+ .flatMap(s -> s.getAuthenticatedClientSessions().values().stream())
+ .map(AuthenticatedClientSessionModel::getClient)
+ .peek(client -> inUseClients.add(client.getClientId()))
+ .collect(Collectors.toSet()));
List offlineClients = new LinkedList<>();
- List offlineSessions = session.sessions().getOfflineUserSessions(realm, user);
- for(UserSessionModel s : offlineSessions) {
- for(AuthenticatedClientSessionModel a : s.getAuthenticatedClientSessions().values()) {
- ClientModel client = a.getClient();
- clients.add(client);
- offlineClients.add(client.getClientId());
- }
- }
+ clients.addAll(session.sessions().getOfflineUserSessionsStream(realm, user)
+ .flatMap(s -> s.getAuthenticatedClientSessions().values().stream())
+ .map(AuthenticatedClientSessionModel::getClient)
+ .peek(client -> offlineClients.add(client.getClientId()))
+ .collect(Collectors.toSet()));
Map consentModels = new HashMap<>();
- session.users().getConsentsStream(realm, user.getId()).forEach(consent -> {
- ClientModel client = consent.getClient();
- clients.add(client);
- consentModels.put(client.getClientId(), consent);
- });
+ clients.addAll(session.users().getConsentsStream(realm, user.getId())
+ .peek(consent -> consentModels.put(consent.getClient().getClientId(), consent))
+ .map(UserConsentModel::getClient)
+ .collect(Collectors.toSet()));
realm.getAlwaysDisplayInConsoleClientsStream().forEach(clients::add);
- List apps = new LinkedList<>();
- for (ClientModel client : clients) {
- if (client.isBearerOnly() || client.getBaseUrl() == null || client.getBaseUrl().isEmpty()) {
- continue;
- }
- else if (matches(client, name)) {
- apps.add(modelToRepresentation(client, inUseClients, offlineClients, consentModels));
- }
- }
-
- return apps;
+ return clients.stream().filter(client -> !client.isBearerOnly() && client.getBaseUrl() != null && !client.getClientId().isEmpty())
+ .filter(client -> matches(client, name))
+ .map(client -> modelToRepresentation(client, inUseClients, offlineClients, consentModels));
}
private boolean matches(ClientModel client, String name) {
diff --git a/services/src/main/java/org/keycloak/services/resources/account/SessionResource.java b/services/src/main/java/org/keycloak/services/resources/account/SessionResource.java
index 46990a9d5b..5d816118c3 100755
--- a/services/src/main/java/org/keycloak/services/resources/account/SessionResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/SessionResource.java
@@ -30,6 +30,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
@@ -73,8 +74,8 @@ public class SessionResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
- public List toRepresentation() {
- return session.sessions().getUserSessions(realm, user).stream().map(this::toRepresentation).collect(Collectors.toList());
+ public Stream toRepresentation() {
+ return session.sessions().getUserSessionsStream(realm, user).map(this::toRepresentation);
}
/**
@@ -88,33 +89,31 @@ public class SessionResource {
@NoCache
public Collection devices() {
Map reps = new HashMap<>();
- List sessions = session.sessions().getUserSessions(realm, user);
+ session.sessions().getUserSessionsStream(realm, user).forEach(s -> {
+ DeviceRepresentation device = getAttachedDevice(s);
+ DeviceRepresentation rep = reps
+ .computeIfAbsent(device.getOs() + device.getOsVersion(), key -> {
+ DeviceRepresentation representation = new DeviceRepresentation();
- for (UserSessionModel s : sessions) {
- DeviceRepresentation device = getAttachedDevice(s);
- DeviceRepresentation rep = reps
- .computeIfAbsent(device.getOs() + device.getOsVersion(), key -> {
- DeviceRepresentation representation = new DeviceRepresentation();
+ representation.setLastAccess(device.getLastAccess());
+ representation.setOs(device.getOs());
+ representation.setOsVersion(device.getOsVersion());
+ representation.setDevice(device.getDevice());
+ representation.setMobile(device.isMobile());
- representation.setLastAccess(device.getLastAccess());
- representation.setOs(device.getOs());
- representation.setOsVersion(device.getOsVersion());
- representation.setDevice(device.getDevice());
- representation.setMobile(device.isMobile());
+ return representation;
+ });
- return representation;
- });
+ if (isCurrentSession(s)) {
+ rep.setCurrent(true);
+ }
- if (isCurrentSession(s)) {
- rep.setCurrent(true);
- }
+ if (rep.getLastAccess() == 0 || rep.getLastAccess() < s.getLastSessionRefresh()) {
+ rep.setLastAccess(s.getLastSessionRefresh());
+ }
- if (rep.getLastAccess() == 0 || rep.getLastAccess() < s.getLastSessionRefresh()) {
- rep.setLastAccess(s.getLastSessionRefresh());
- }
-
- rep.addSession(createSessionRepresentation(s, device));
- }
+ rep.addSession(createSessionRepresentation(s, device));
+ });
return reps.values();
}
@@ -130,13 +129,9 @@ public class SessionResource {
@NoCache
public Response logout(@QueryParam("current") boolean removeCurrent) {
auth.require(AccountRoles.MANAGE_ACCOUNT);
- List userSessions = session.sessions().getUserSessions(realm, user);
-
- for (UserSessionModel s : userSessions) {
- if (removeCurrent || !isCurrentSession(s)) {
- AuthenticationManager.backchannelLogout(session, s, true);
- }
- }
+ session.sessions().getUserSessionsStream(realm, user).filter(s -> removeCurrent || !isCurrentSession(s))
+ .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
+ .forEach(s -> AuthenticationManager.backchannelLogout(session, s, true));
return Response.noContent().build();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 567ce955af..1a7eca2f05 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -50,10 +50,8 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ErrorResponseException;
-import org.keycloak.services.clientpolicy.AdminClientRegisterContext;
import org.keycloak.services.clientpolicy.AdminClientUpdateContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
-import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
import org.keycloak.services.managers.ClientManager;
@@ -78,11 +76,12 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
import static java.lang.Boolean.TRUE;
@@ -462,17 +461,13 @@ public class ClientResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List getUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
+ public Stream getUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
auth.clients().requireView(client);
firstResult = firstResult != null ? firstResult : -1;
maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
- List sessions = new ArrayList();
- for (UserSessionModel userSession : session.sessions().getUserSessions(client.getRealm(), client, firstResult, maxResults)) {
- UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
- sessions.add(rep);
- }
- return sessions;
+ return session.sessions().getUserSessionsStream(client.getRealm(), client, firstResult, maxResults)
+ .map(ModelToRepresentation::toRepresentation);
}
/**
@@ -511,30 +506,14 @@ public class ClientResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List getOfflineUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
+ public Stream getOfflineUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
auth.clients().requireView(client);
firstResult = firstResult != null ? firstResult : -1;
maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
- List sessions = new ArrayList();
- List userSessions = session.sessions().getOfflineUserSessions(client.getRealm(), client, firstResult, maxResults);
- for (UserSessionModel userSession : userSessions) {
- UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
- // Update lastSessionRefresh with the timestamp from clientSession
- for (Map.Entry csEntry : userSession.getAuthenticatedClientSessions().entrySet()) {
- String clientUuid = csEntry.getKey();
- AuthenticatedClientSessionModel clientSession = csEntry.getValue();
-
- if (client.getId().equals(clientUuid)) {
- rep.setLastAccess(Time.toMillis(clientSession.getTimestamp()));
- break;
- }
- }
-
- sessions.add(rep);
- }
- return sessions;
+ return session.sessions().getOfflineUserSessionsStream(client.getRealm(), client, firstResult, maxResults)
+ .map(this::toUserSessionRepresentation);
}
/**
@@ -701,4 +680,23 @@ public class ClientResource {
authorization().disable();
}
}
+
+ /**
+ * Converts the specified {@link UserSessionModel} into a {@link UserSessionRepresentation}.
+ *
+ * @param userSession the model to be converted.
+ * @return a reference to the constructed representation.
+ */
+ private UserSessionRepresentation toUserSessionRepresentation(final UserSessionModel userSession) {
+ UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
+
+ // Update lastSessionRefresh with the timestamp from clientSession
+ Map.Entry result = userSession.getAuthenticatedClientSessions().entrySet().stream()
+ .filter(entry -> Objects.equals(client.getId(), entry.getKey()))
+ .findFirst().orElse(null);
+ if (result != null) {
+ rep.setLastAccess(Time.toMillis(result.getValue().getTimestamp()));
+ }
+ return rep;
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
index 1c5cc64a05..0b3458bac6 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
@@ -309,15 +309,9 @@ public class UserResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List getSessions() {
+ public Stream getSessions() {
auth.users().requireView(user);
- List sessions = session.sessions().getUserSessions(realm, user);
- List reps = new ArrayList();
- for (UserSessionModel session : sessions) {
- UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
- reps.add(rep);
- }
- return reps;
+ return session.sessions().getUserSessionsStream(realm, user).map(ModelToRepresentation::toRepresentation);
}
/**
@@ -329,30 +323,15 @@ public class UserResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List getOfflineSessions(final @PathParam("clientUuid") String clientUuid) {
+ public Stream getOfflineSessions(final @PathParam("clientUuid") String clientUuid) {
auth.users().requireView(user);
ClientModel client = realm.getClientById(clientUuid);
if (client == null) {
throw new NotFoundException("Client not found");
}
- List sessions = new UserSessionManager(session).findOfflineSessions(realm, user);
- List reps = new ArrayList();
- for (UserSessionModel session : sessions) {
- UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
-
- // Update lastSessionRefresh with the timestamp from clientSession
- AuthenticatedClientSessionModel clientSession = session.getAuthenticatedClientSessionByClient(clientUuid);
-
- // Skip if userSession is not for this client
- if (clientSession == null) {
- continue;
- }
-
- rep.setLastAccess(clientSession.getTimestamp());
-
- reps.add(rep);
- }
- return reps;
+ return new UserSessionManager(session).findOfflineSessionsStream(realm, user)
+ .map(session -> toUserSessionRepresentation(session, clientUuid))
+ .filter(Objects::nonNull);
}
/**
@@ -503,10 +482,10 @@ public class UserResource {
session.users().setNotBeforeForUser(realm, user, Time.currentTime());
- List userSessions = session.sessions().getUserSessions(realm, user);
- for (UserSessionModel userSession : userSessions) {
- AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, true);
- }
+ session.sessions().getUserSessionsStream(realm, user)
+ .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
+ .forEach(userSession -> AuthenticationManager.backchannelLogout(session, realm, userSession,
+ session.getContext().getUri(), clientConnection, headers, true));
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).success();
}
@@ -900,4 +879,22 @@ public class UserResource {
}
}
+ /**
+ * Converts the specified {@link UserSessionModel} into a {@link UserSessionRepresentation}.
+ *
+ * @param userSession the model to be converted.
+ * @param clientUuid the client's UUID.
+ * @return a reference to the constructed representation or {@code null} if the session is not associated with the specified
+ * client.
+ */
+ private UserSessionRepresentation toUserSessionRepresentation(final UserSessionModel userSession, final String clientUuid) {
+ UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
+ // Update lastSessionRefresh with the timestamp from clientSession
+ AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(clientUuid);
+ if (clientSession == null) {
+ return null;
+ }
+ rep.setLastAccess(clientSession.getTimestamp());
+ return rep;
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
index 572f8672e9..aa718c73c9 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
@@ -282,7 +282,7 @@ public class ImpersonationTest extends AbstractKeycloakTest {
final UserSessionNotesHolder notesHolder = testingClient.server("test").fetch(session -> {
final RealmModel realm = session.realms().getRealmByName("test");
final UserModel user = session.users().getUserById(userId, realm);
- final UserSessionModel userSession = session.sessions().getUserSessions(realm, user).get(0);
+ final UserSessionModel userSession = session.sessions().getUserSessionsStream(realm, user).findFirst().get();
return new UserSessionNotesHolder(userSession.getNotes());
}, UserSessionNotesHolder.class);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/BrokerRunOnServerUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/BrokerRunOnServerUtil.java
index ab47c0f2e0..3ea2533f35 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/BrokerRunOnServerUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/BrokerRunOnServerUtil.java
@@ -135,8 +135,7 @@ final class BrokerRunOnServerUtil {
return (session) -> {
RealmModel realm = session.realms().getRealmByName("consumer");
UserModel user = session.users().getUserByUsername("testuser", realm);
- List userSessions = session.sessions().getUserSessions(realm, user);
- UserSessionModel sessions = userSessions.get(0);
+ UserSessionModel sessions = session.sessions().getUserSessionsStream(realm, user).findFirst().get();
assertEquals("sessionvalue", sessions.getNote("user-session-attr"));
};
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
index 45279240bb..38a9e4321f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
@@ -42,6 +42,7 @@ import org.keycloak.testsuite.arquillian.annotation.ModelTest;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
@@ -116,7 +117,8 @@ public class UserSessionInitializerTest extends AbstractTestRealmKeycloakTest {
assertThat("Count of offline sesions for client 'test-app'", currentSession.sessions().getOfflineSessionsCount(realm, testApp), is((long) 3));
assertThat("Count of offline sesions for client 'third-party'", currentSession.sessions().getOfflineSessionsCount(realm, thirdparty), is((long) 1));
- List loadedSessions = currentSession.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
+ List loadedSessions = currentSession.sessions().getOfflineUserSessionsStream(realm, testApp, 0, 10)
+ .collect(Collectors.toList());
UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
assertSessionLoaded(loadedSessions, origSessions[0].getId(), currentSession.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
@@ -166,7 +168,8 @@ public class UserSessionInitializerTest extends AbstractTestRealmKeycloakTest {
ClientModel thirdparty = realm.getClientByClientId("third-party");
assertThat("Count of offline sesions for client 'third-party'", currentSession.sessions().getOfflineSessionsCount(realm, thirdparty), is((long) 1));
- List loadedSessions = currentSession.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
+ List loadedSessions = currentSession.sessions().getOfflineUserSessionsStream(realm, thirdparty, 0, 10)
+ .collect(Collectors.toList());
assertThat("Size of loaded Sessions", loadedSessions.size(), is(1));
assertSessionLoaded(loadedSessions, origSessions[0].getId(), currentSession.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "third-party");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
index a65bed4bd4..296abf1ddd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
@@ -44,6 +44,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -102,10 +103,8 @@ public class UserSessionPersisterProviderTest extends AbstractTestRealmKeycloakT
// Persist 3 created userSessions and clientSessions as offline
RealmModel realm = sessionWL22.realms().getRealm("test");
ClientModel testApp = realm.getClientByClientId("test-app");
- List userSessions = sessionWL22.sessions().getUserSessions(realm, testApp);
- for (UserSessionModel userSessionLooper : userSessions) {
- persistUserSession(sessionWL22, userSessionLooper, true);
- }
+ sessionWL22.sessions().getUserSessionsStream(realm, testApp).collect(Collectors.toList())
+ .forEach(userSessionLooper -> persistUserSession(sessionWL22, userSessionLooper, true));
});
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL2) -> {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
index f9cda20dbc..969df8b04f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
@@ -49,6 +49,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -112,10 +113,8 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
// Key is userSession ID, values are client UUIDS
// Persist 3 created userSessions and clientSessions as offline
ClientModel testApp = realm.getClientByClientId("test-app");
- List userSessions = currentSession.sessions().getUserSessions(realm, testApp);
- for (UserSessionModel userSession : userSessions) {
- offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(currentSession, userSession));
- }
+ currentSession.sessions().getUserSessionsStream(realm, testApp).collect(Collectors.toList())
+ .forEach(userSession -> offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(currentSession, userSession)));
});
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCrud3) -> {
@@ -170,7 +169,8 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
Assert.assertEquals(2, currentSession.sessions().getOfflineSessionsCount(realm, testApp));
Assert.assertEquals(1, currentSession.sessions().getOfflineSessionsCount(realm, thirdparty));
- List thirdpartySessions = currentSession.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
+ List thirdpartySessions = currentSession.sessions().getOfflineUserSessionsStream(realm, thirdparty, 0, 10)
+ .collect(Collectors.toList());
Assert.assertEquals(1, thirdpartySessions.size());
Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
@@ -203,7 +203,8 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
Assert.assertEquals(1, currentSession.sessions().getOfflineSessionsCount(realm, testApp));
Assert.assertEquals(0, currentSession.sessions().getOfflineSessionsCount(realm, thirdparty));
- List testAppSessions = currentSession.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
+ List testAppSessions = currentSession.sessions().getOfflineUserSessionsStream(realm, testApp, 0, 10)
+ .collect(Collectors.toList());
Assert.assertEquals(1, testAppSessions.size());
Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
@@ -462,10 +463,8 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
// Persist 3 created userSessions and clientSessions as offline
testApp[0] = realm.getClientByClientId("test-app");
- List userSessions = currentSession.sessions().getUserSessions(realm, testApp[0]);
- for (UserSessionModel userSession : userSessions) {
- offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(currentSession, userSession));
- }
+ currentSession.sessions().getUserSessionsStream(realm, testApp[0]).collect(Collectors.toList())
+ .forEach(userSession -> offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(currentSession, userSession)));
// Assert all previously saved offline sessions found
for (Map.Entry> entry : offlineSessions.entrySet()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index b69409d458..a850384b82 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -46,10 +46,12 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -249,8 +251,10 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
}
- assertSessions(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)), sessions[0], sessions[1]);
- assertSessions(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)), sessions[2]);
+ assertSessions(session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername("user1", realm))
+ .collect(Collectors.toList()), sessions[0], sessions[1]);
+ assertSessions(session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername("user2", realm))
+ .collect(Collectors.toList()), sessions[2]);
}
@Test
@@ -262,20 +266,18 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
inheritClientConnection(session, kcSession);
createSessions(kcSession);
});
- Map clientSessionsKept = new HashMap<>();
- for (UserSessionModel s : session.sessions().getUserSessions(realm,
- session.users().getUserByUsername("user2", realm))) {
-
- clientSessionsKept.put(s.getId(), s.getAuthenticatedClientSessions().keySet().size());
- }
+ Map clientSessionsKept = session.sessions().getUserSessionsStream(realm,
+ session.users().getUserByUsername("user2", realm))
+ .collect(Collectors.toMap(model -> model.getId(), model -> model.getAuthenticatedClientSessions().keySet().size()));
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession kcSession) -> {
kcSession.sessions().removeUserSessions(realm, kcSession.users().getUserByUsername("user1", realm));
});
- assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
- List userSessions = session.sessions().getUserSessions(realm,
- session.users().getUserByUsername("user2", realm));
+ assertEquals(0, session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername("user1", realm))
+ .count());
+ List userSessions = session.sessions().getUserSessionsStream(realm,
+ session.users().getUserByUsername("user2", realm)).collect(Collectors.toList());
assertSame(userSessions.size(), 1);
@@ -309,8 +311,10 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
kcSession.sessions().removeUserSessions(realm);
});
- assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
- assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
+ assertEquals(0, session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername("user1", realm))
+ .count());
+ assertEquals(0, session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername("user2", realm))
+ .count());
}
@Test
@@ -544,8 +548,10 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
}
- assertSessions(session.sessions().getUserSessions(realm, realm.getClientByClientId("test-app")), sessions[0], sessions[1], sessions[2]);
- assertSessions(session.sessions().getUserSessions(realm, realm.getClientByClientId("third-party")), sessions[0]);
+ assertSessions(session.sessions().getUserSessionsStream(realm, realm.getClientByClientId("test-app"))
+ .collect(Collectors.toList()), sessions[0], sessions[1], sessions[2]);
+ assertSessions(session.sessions().getUserSessionsStream(realm, realm.getClientByClientId("third-party"))
+ .collect(Collectors.toList()), sessions[0]);
}
@Test
@@ -663,7 +669,7 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
}
private static void assertPaginatedSession(KeycloakSession session, RealmModel realm, ClientModel client, int start, int max, int expectedSize) {
- List sessions = session.sessions().getUserSessions(realm, client, start, max);
+ List sessions = session.sessions().getUserSessionsStream(realm, client, start, max).collect(Collectors.toList());
String[] actualIps = new String[sessions.size()];
for (int i = 0; i < actualIps.length; i++) {
@@ -773,11 +779,11 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
session.userStorageManager().removeUser(realm, user1);
- assertTrue(session.sessions().getUserSessions(realm, user1).isEmpty());
+ assertEquals(0, session.sessions().getUserSessionsStream(realm, user1).count());
session.getTransactionManager().commit();
- assertFalse(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
+ assertNotEquals(0, session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername("user2", realm)).count());
user1 = session.users().getUserByUsername("user1", realm);
user2 = session.users().getUserByUsername("user2", realm);