[KEYCLOAK-16508] Complement methods for accessing user sessions with Stream variants

This commit is contained in:
Stefan Guilhen 2020-12-01 15:11:31 -03:00 committed by Hynek Mlnařík
parent edabbc9449
commit d6422e415c
20 changed files with 406 additions and 378 deletions

View file

@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -270,25 +269,16 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
protected List<UserSessionModel> getUserSessions(RealmModel realm, Predicate<Map.Entry<String, SessionEntityWrapper<UserSessionEntity>>> predicate, boolean offline) {
protected Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, Predicate<Map.Entry<String, SessionEntityWrapper<UserSessionEntity>>> predicate, boolean offline) {
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
cache = CacheDecorators.skipCacheLoaders(cache);
Stream<Map.Entry<String, SessionEntityWrapper<UserSessionEntity>>> cacheStream = cache.entrySet().stream();
List<UserSessionModel> resultSessions = new LinkedList<>();
Iterator<UserSessionEntity> 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<UserSessionModel> getUserSessions(final RealmModel realm, UserModel user) {
return getUserSessions(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), false);
public Stream<UserSessionModel> getUserSessionsStream(final RealmModel realm, UserModel user) {
return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), false);
}
@Override
public List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
return getUserSessions(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), false);
public Stream<UserSessionModel> getUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) {
return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), false);
}
@Override
public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
List<UserSessionModel> 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<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
return getUserSessions(realm, client, -1, -1);
public Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client) {
return getUserSessionsStream(realm, client, -1, -1);
}
@Override
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
return getUserSessions(realm, client, firstResult, maxResults, false);
public Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
return getUserSessionsStream(realm, client, firstResult, maxResults, false);
}
protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
protected Stream<UserSessionModel> 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<UserSessionModel> getUserSessionModels(RealmModel realm, int firstResult, int maxResults, boolean offline, UserSessionPredicate predicate) {
protected Stream<UserSessionModel> getUserSessionModels(RealmModel realm, int firstResult, int maxResults, boolean offline, UserSessionPredicate predicate) {
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
cache = CacheDecorators.skipCacheLoaders(cache);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = getClientSessionCache(offline);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCacheDecorated = CacheDecorators.skipCacheLoaders(clientSessionCache);
Stream<UserSessionEntity> 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<UserSessionEntity> 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<UserSessionModel> sessions = new LinkedList<>();
Iterator<UserSessionEntity> 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<UserSessionModel> 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<UserSessionModel> getOfflineUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
return getUserSessions(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), true);
public Stream<UserSessionModel> 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<UserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
List<UserSessionModel> userSessions = new LinkedList<>();
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = CacheDecorators.skipCacheLoaders(offlineSessionCache);
Iterator<UserSessionEntity> 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<UserSessionModel> 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<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max) {
return getUserSessions(realm, client, first, max, true);
public Stream<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, ClientModel client, int first, int max) {
return getUserSessionsStream(realm, client, first, max, true);
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -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<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client);
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults);
List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId);
/**
* @deprecated Use {@link #getUserSessionsStream(RealmModel, ClientModel) getUserSessionsStream} instead.
*/
@Deprecated
default List<UserSessionModel> 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<UserSessionModel> getUserSessionsStream(RealmModel realm, UserModel user);
/**
* @deprecated Use {@link #getUserSessionsStream(RealmModel, ClientModel) getUserSessionsStream} instead.
*/
@Deprecated
default List<UserSessionModel> 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<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client);
/**
* @deprecated Use {@link #getUserSessionsStream(RealmModel, ClientModel, int, int) getUserSessionsStream} instead.
*/
@Deprecated
default List<UserSessionModel> 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<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client, int firstResult, int maxResults);
/**
* @deprecated Use {@link #getUserSessionByBrokerUserIdStream(RealmModel, String) getUserSessionByBrokerUserIdStream}
* instead.
*/
@Deprecated
default List<UserSessionModel> 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<UserSessionModel> 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<UserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user);
/**
* @deprecated Use {@link #getOfflineUserSessionsStream(RealmModel, UserModel) getOfflineUserSessionsStream} instead.
*/
@Deprecated
default List<UserSessionModel> 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<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, UserModel user);
UserSessionModel getOfflineUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId);
List<UserSessionModel> getOfflineUserSessionByBrokerUserId(RealmModel realm, String brokerUserId);
/**
* @deprecated Use {@link #getOfflineUserSessionByBrokerUserIdStream(RealmModel, String) getOfflineUserSessionByBrokerUserIdStream}
* instead.
*/
@Deprecated
default List<UserSessionModel> 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<UserSessionModel> getOfflineUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId);
long getOfflineSessionsCount(RealmModel realm, ClientModel client);
List<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
/**
* @deprecated use {@link #getOfflineUserSessionsStream(RealmModel, ClientModel, int, int) getOfflineUserSessionsStream}
* instead.
*/
@Deprecated
default List<UserSessionModel> 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<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, ClientModel client, int firstResult, int maxResults);
/** Triggered by persister during pre-load. It imports authenticatedClientSessions too **/
void importUserSessions(Collection<UserSessionModel> persistentUserSessions, boolean offline);

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -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<UserSessionModel> 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 {

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -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<UserSessionModel> 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<SamlAuthenticationPreprocessor> 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<LogoutRequestType> 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<UserSessionModel> processLogout(AtomicReference<LogoutRequestType> ref) {
return userSession -> {
for(Iterator<SamlAuthenticationPreprocessor> 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 {

View file

@ -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<UserSessionModel> 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<UserSessionModel> 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) {

View file

@ -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<UserSessionModel> 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() {

View file

@ -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<UserSessionModel> 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,

View file

@ -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<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
logoutUserSessions(realm, userSessions);
}
protected void logoutUserSessions(RealmModel realm, List<UserSessionModel> userSessions) {
// Map from "app" to clientSessions for this app
MultivaluedHashMap<String, AuthenticatedClientSessionModel> 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<String, List<AuthenticatedClientSessionModel>> entry : clientSessions.entrySet()) {
if (entry.getValue().size() == 0) {
continue;
}
logoutClientSessions(realm, entry.getValue().get(0).getClient(), entry.getValue());
}
}
private void putClientSessions(MultivaluedHashMap<String, AuthenticatedClientSessionModel> clientSessions, UserSessionModel userSession) {
for (Map.Entry<String, AuthenticatedClientSessionModel> 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));
}

View file

@ -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<ClientModel> findClientsWithOfflineToken(RealmModel realm, UserModel user) {
List<UserSessionModel> userSessions = kcSession.sessions().getOfflineUserSessions(realm, user);
Set<ClientModel> clients = new HashSet<>();
for (UserSessionModel userSession : userSessions) {
Set<String> 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<UserSessionModel> findOfflineSessions(RealmModel realm, UserModel user) {
return kcSession.sessions().getOfflineUserSessions(realm, user);
return this.findOfflineSessionsStream(realm, user).collect(Collectors.toList());
}
public Stream<UserSessionModel> findOfflineSessionsStream(RealmModel realm, UserModel user) {
return kcSession.sessions().getOfflineUserSessionsStream(realm, user);
}
public boolean revokeOfflineToken(UserModel user, ClientModel client) {
RealmModel realm = client.getRealm();
List<UserSessionModel> 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) {

View file

@ -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<UserSessionModel> 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<UserSessionModel> 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();

View file

@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -401,51 +401,36 @@ public class AccountRestService {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public List<ClientRepresentation> applications(@QueryParam("name") String name) {
public Stream<ClientRepresentation> applications(@QueryParam("name") String name) {
checkAccountApiEnabled();
auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_APPLICATIONS);
Set<ClientModel> clients = new HashSet<>();
List<String> inUseClients = new LinkedList<>();
List<UserSessionModel> 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<String> offlineClients = new LinkedList<>();
List<UserSessionModel> 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<String, UserConsentModel> 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<ClientRepresentation> 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) {

View file

@ -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<SessionRepresentation> toRepresentation() {
return session.sessions().getUserSessions(realm, user).stream().map(this::toRepresentation).collect(Collectors.toList());
public Stream<SessionRepresentation> toRepresentation() {
return session.sessions().getUserSessionsStream(realm, user).map(this::toRepresentation);
}
/**
@ -88,33 +89,31 @@ public class SessionResource {
@NoCache
public Collection<DeviceRepresentation> devices() {
Map<String, DeviceRepresentation> reps = new HashMap<>();
List<UserSessionModel> 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<UserSessionModel> 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();
}

View file

@ -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<UserSessionRepresentation> getUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
public Stream<UserSessionRepresentation> 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<UserSessionRepresentation> sessions = new ArrayList<UserSessionRepresentation>();
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<UserSessionRepresentation> getOfflineUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
public Stream<UserSessionRepresentation> 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<UserSessionRepresentation> sessions = new ArrayList<UserSessionRepresentation>();
List<UserSessionModel> 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<String, AuthenticatedClientSessionModel> 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<String, AuthenticatedClientSessionModel> 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;
}
}

View file

@ -309,15 +309,9 @@ public class UserResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<UserSessionRepresentation> getSessions() {
public Stream<UserSessionRepresentation> getSessions() {
auth.users().requireView(user);
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
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<UserSessionRepresentation> getOfflineSessions(final @PathParam("clientUuid") String clientUuid) {
public Stream<UserSessionRepresentation> 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<UserSessionModel> sessions = new UserSessionManager(session).findOfflineSessions(realm, user);
List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
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<UserSessionModel> 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;
}
}

View file

@ -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);

View file

@ -135,8 +135,7 @@ final class BrokerRunOnServerUtil {
return (session) -> {
RealmModel realm = session.realms().getRealmByName("consumer");
UserModel user = session.users().getUserByUsername("testuser", realm);
List<UserSessionModel> 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"));
};
}

View file

@ -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<UserSessionModel> loadedSessions = currentSession.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
List<UserSessionModel> 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<UserSessionModel> loadedSessions = currentSession.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
List<UserSessionModel> 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");

View file

@ -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<UserSessionModel> 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) -> {

View file

@ -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<UserSessionModel> 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<UserSessionModel> thirdpartySessions = currentSession.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
List<UserSessionModel> 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<UserSessionModel> testAppSessions = currentSession.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
List<UserSessionModel> 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<UserSessionModel> 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<String, Set<String>> entry : offlineSessions.entrySet()) {

View file

@ -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<String, Integer> clientSessionsKept = new HashMap<>();
for (UserSessionModel s : session.sessions().getUserSessions(realm,
session.users().getUserByUsername("user2", realm))) {
clientSessionsKept.put(s.getId(), s.getAuthenticatedClientSessions().keySet().size());
}
Map<String, Integer> 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<UserSessionModel> userSessions = session.sessions().getUserSessions(realm,
session.users().getUserByUsername("user2", realm));
assertEquals(0, session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername("user1", realm))
.count());
List<UserSessionModel> 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<UserSessionModel> sessions = session.sessions().getUserSessions(realm, client, start, max);
List<UserSessionModel> 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);