mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 15:02:05 -03:30
Replace UUID with composite key for client session cache
Closes #42547 Signed-off-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com> Signed-off-by: Alexander Schwartz <aschwart@redhat.com> Co-authored-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com> Co-authored-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
d9b4bd047f
commit
f7ff7e55d8
@ -111,6 +111,11 @@ configured directly by {project_name}:
|
||||
Configuring ports using the old
|
||||
properties has not changed, but using the CLI options is recommended because the previous method could be deprecated.
|
||||
|
||||
=== Internal representation of client sessions changed
|
||||
|
||||
The cache key of the authenticated client sessions has changed for embedded Infinispan, while the public APIs have not changed.
|
||||
Due to this, you should not run 26.4.x concurrently in a cluster with previous versions.
|
||||
|
||||
=== External IDP tokens automatically refreshed
|
||||
|
||||
When using the `+/realms/{realm-name}/broker/{provider_alias}/token+` endpoint for an OAuth 2.0 IDP that provides refresh tokens and JSON responses or for OIDC IDPs, the tokens will be automatically refreshed each time they are retrieved via the endpoint if the access token has expired and the IDP provided a refresh token.
|
||||
@ -256,6 +261,16 @@ Configuration of the default cache configurations in `conf/cache-ispn.xml`, or i
|
||||
|
||||
In a future major release, the start-up will fail if default cache configurations are stated in those files and the option is not specified.
|
||||
|
||||
=== Simplified API for UserSessionProvider
|
||||
|
||||
In order to retrieve a client session via `UserSessionProvider#getClientSession`, you no longer need to pass in the client session ID.
|
||||
The old methods have been deprecated and will be removed in a future release.
|
||||
You should also review the other methods that are deprecated for removal in this class.
|
||||
|
||||
=== Simplified API for AuthenticatedClientSessionModel
|
||||
|
||||
The `clientId` note in the authenticated client session is an internal note present only when using the embedded caches, and is now deprecated for removal. Instead, use the `getClient()` method.
|
||||
|
||||
// ------------------------ Removed features ------------------------ //
|
||||
== Removed features
|
||||
|
||||
|
||||
@ -85,6 +85,7 @@ import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessi
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientSessionKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.EmbeddedClientSessionKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.RemoteAuthenticatedClientSessionEntity;
|
||||
@ -206,6 +207,7 @@ import org.keycloak.storage.managers.UserStorageSyncManager;
|
||||
AuthenticatedClientSessionEntity.class,
|
||||
AuthenticationSessionEntity.class,
|
||||
ClientSessionKey.class,
|
||||
EmbeddedClientSessionKey.class,
|
||||
LoginFailureEntity.class,
|
||||
LoginFailureKey.class,
|
||||
RemoteAuthenticatedClientSessionEntity.class,
|
||||
|
||||
@ -182,6 +182,8 @@ public final class Marshalling {
|
||||
|
||||
public static final int RELOAD_CERTIFICATE_FUNCTION = 65615;
|
||||
|
||||
public static final int EMBEDDED_CLIENT_SESSION_KEY = 65616;
|
||||
|
||||
public static void configure(GlobalConfigurationBuilder builder) {
|
||||
getSchemas().forEach(builder.serialization()::addContextInitializer);
|
||||
}
|
||||
|
||||
@ -17,9 +17,8 @@
|
||||
|
||||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
@ -33,8 +32,7 @@ import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionsChangelogBasedTransaction;
|
||||
import org.keycloak.models.sessions.infinispan.changes.Tasks;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.keycloak.models.sessions.infinispan.entities.EmbeddedClientSessionKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
@ -42,15 +40,18 @@ import java.util.UUID;
|
||||
public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
|
||||
|
||||
private final KeycloakSession kcSession;
|
||||
private AuthenticatedClientSessionEntity entity;
|
||||
private final AuthenticatedClientSessionEntity entity;
|
||||
private final ClientModel client;
|
||||
private final SessionsChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx;
|
||||
private final SessionsChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionUpdateTx;
|
||||
private UserSessionModel userSession;
|
||||
private boolean offline;
|
||||
private final boolean offline;
|
||||
private final EmbeddedClientSessionKey cacheKey;
|
||||
|
||||
public AuthenticatedClientSessionAdapter(KeycloakSession kcSession,
|
||||
AuthenticatedClientSessionEntity entity, ClientModel client, UserSessionModel userSession,
|
||||
SessionsChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx, boolean offline) {
|
||||
SessionsChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionUpdateTx,
|
||||
EmbeddedClientSessionKey cacheKey,
|
||||
boolean offline) {
|
||||
if (userSession == null) {
|
||||
throw new NullPointerException("userSession must not be null");
|
||||
}
|
||||
@ -61,10 +62,11 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
|
||||
this.client = client;
|
||||
this.clientSessionUpdateTx = clientSessionUpdateTx;
|
||||
this.offline = offline;
|
||||
this.cacheKey = cacheKey;
|
||||
}
|
||||
|
||||
private void update(ClientSessionUpdateTask task) {
|
||||
clientSessionUpdateTx.addTask(entity.getId(), task);
|
||||
clientSessionUpdateTx.addTask(cacheKey, task);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,7 +86,7 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
|
||||
|
||||
SessionUpdateTask<AuthenticatedClientSessionEntity> removeTask = Tasks.removeSync(offline);
|
||||
|
||||
clientSessionUpdateTx.addTask(entity.getId(), removeTask);
|
||||
clientSessionUpdateTx.addTask(cacheKey, removeTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,7 +119,7 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId().toString();
|
||||
return cacheKey.toId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -258,10 +260,7 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
|
||||
|
||||
@Override
|
||||
public Map<String, String> getNotes() {
|
||||
if (entity.getNotes().isEmpty()) return Collections.emptyMap();
|
||||
Map<String, String> copy = new HashMap<>();
|
||||
copy.putAll(entity.getNotes());
|
||||
return copy;
|
||||
return new ConcurrentHashMap<>(entity.getNotes());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -23,7 +23,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -35,6 +35,7 @@ import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.commons.api.AsyncCache;
|
||||
import org.infinispan.commons.util.concurrent.CompletionStages;
|
||||
import org.infinispan.stream.CacheCollectors;
|
||||
import org.jboss.logging.Logger;
|
||||
@ -61,7 +62,7 @@ import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.changes.Tasks;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.EmbeddedClientSessionKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
||||
@ -77,6 +78,9 @@ import org.keycloak.models.sessions.infinispan.util.InfinispanKeyGenerator;
|
||||
import org.keycloak.models.sessions.infinispan.util.SessionTimeouts;
|
||||
import org.keycloak.utils.StreamsUtil;
|
||||
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.models.Constants.SESSION_NOTE_LIGHTWEIGHT_USER;
|
||||
import static org.keycloak.utils.StreamsUtil.paginatedStream;
|
||||
|
||||
@ -91,8 +95,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
|
||||
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx;
|
||||
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx;
|
||||
protected final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionTx;
|
||||
protected final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> offlineClientSessionTx;
|
||||
protected final InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionTx;
|
||||
protected final InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> offlineClientSessionTx;
|
||||
|
||||
protected final SessionEventsSenderTransaction clusterEventsSenderTx;
|
||||
|
||||
@ -109,8 +113,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
InfinispanKeyGenerator keyGenerator,
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx,
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx,
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionTx,
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> offlineClientSessionTx,
|
||||
InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionTx,
|
||||
InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> offlineClientSessionTx,
|
||||
SessionFunction<UserSessionEntity> offlineSessionCacheEntryLifespanAdjuster,
|
||||
SessionFunction<AuthenticatedClientSessionEntity> offlineClientSessionCacheEntryLifespanAdjuster) {
|
||||
this.session = session;
|
||||
@ -138,11 +142,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
return offline ? offlineSessionTx : sessionTx;
|
||||
}
|
||||
|
||||
protected Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> getClientSessionCache(boolean offline) {
|
||||
protected Cache<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> getClientSessionCache(boolean offline) {
|
||||
return offline ? offlineClientSessionTx.getCache() : clientSessionTx.getCache();
|
||||
}
|
||||
|
||||
protected InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> getClientSessionTransaction(boolean offline) {
|
||||
protected InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> getClientSessionTransaction(boolean offline) {
|
||||
return offline ? offlineClientSessionTx : clientSessionTx;
|
||||
}
|
||||
|
||||
@ -158,20 +162,20 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
|
||||
@Override
|
||||
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = clientSessionTx;
|
||||
final UUID clientSessionId = keyGenerator.generateKeyUUID(session, clientSessionUpdateTx.getCache());
|
||||
var entity = AuthenticatedClientSessionEntity.create(clientSessionId, realm, client, userSession);
|
||||
InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionUpdateTx = clientSessionTx;
|
||||
final EmbeddedClientSessionKey key = new EmbeddedClientSessionKey(userSession.getId(), client.getId());
|
||||
var entity = AuthenticatedClientSessionEntity.create(realm, client, userSession);
|
||||
|
||||
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(session, entity, client, userSession, clientSessionUpdateTx, false);
|
||||
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(session, entity, client, userSession, clientSessionUpdateTx, key, false);
|
||||
|
||||
// For now, the clientSession is considered transient in case that userSession was transient
|
||||
UserSessionModel.SessionPersistenceState persistenceState = userSession.getPersistenceState() != null ?
|
||||
userSession.getPersistenceState() : UserSessionModel.SessionPersistenceState.PERSISTENT;
|
||||
|
||||
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
|
||||
clientSessionUpdateTx.addTask(clientSessionId, createClientSessionTask, entity, persistenceState);
|
||||
clientSessionUpdateTx.addTask(key, createClientSessionTask, entity, persistenceState);
|
||||
|
||||
sessionTx.addTask(userSession.getId(), new RegisterClientSessionTask(client.getId(), clientSessionId));
|
||||
sessionTx.addTask(userSession.getId(), new RegisterClientSessionTask(key.clientId()));
|
||||
|
||||
return adapter;
|
||||
}
|
||||
@ -208,6 +212,21 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
if ("26.0.0".equals(modelVersion)) {
|
||||
log.debug("Clear caches to migrate to Infinispan Protostream");
|
||||
CompletionStages.join(session.getProvider(InfinispanConnectionProvider.class).migrateToProtoStream());
|
||||
} else if ("26.4.0".equals(modelVersion)) {
|
||||
log.debug("Clear caches as client session entries are now outdated and are not migrated");
|
||||
// This is a best-effort approach: Even if due to a rolling update some entries are left there, the checking of sessions and tokens does not depend on them.
|
||||
// Refreshing of tokens will still work even if the user session does not contain the list of client sessions.
|
||||
// This still keeps the user session cache to keep users logged in on a best effort basis.
|
||||
// Only the offline user sessions cache is cleared, but not the regular user sessions cache is cleared.
|
||||
// All client session caches regular and offline client sessions are cleared as usual.
|
||||
var stage = CompletionStages.aggregateCompletionStage();
|
||||
var provider = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Stream.of(OFFLINE_USER_SESSION_CACHE_NAME, CLIENT_SESSION_CACHE_NAME, OFFLINE_CLIENT_SESSION_CACHE_NAME)
|
||||
.map(s -> provider.getCache(s, false))
|
||||
.filter(Objects::nonNull)
|
||||
.map(AsyncCache::clearAsync)
|
||||
.forEach(stage::dependsOn);
|
||||
CompletionStages.join(stage.freeze());
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,25 +316,26 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
return userSessionEntityToImport;
|
||||
}
|
||||
|
||||
private Map<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> computeClientSessionsToImport(UserSessionModel persistentUserSession, UserSessionEntity userSessionToImport) {
|
||||
Map<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsById = new HashMap<>();
|
||||
AuthenticatedClientSessionStore clientSessions = userSessionToImport.getAuthenticatedClientSessions();
|
||||
private Map<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> computeClientSessionsToImport(UserSessionModel persistentUserSession, UserSessionEntity userSessionToImport) {
|
||||
Map<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsById = new HashMap<>();
|
||||
Set<String> clientSessions = userSessionToImport.getClientSessions();
|
||||
String userSessionId = userSessionToImport.getId();
|
||||
int lastSessionRefresh = userSessionToImport.getLastSessionRefresh();
|
||||
String realmId = userSessionToImport.getRealmId();
|
||||
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : persistentUserSession.getAuthenticatedClientSessions().entrySet()) {
|
||||
String clientUUID = entry.getKey();
|
||||
AuthenticatedClientSessionModel clientSession = entry.getValue();
|
||||
AuthenticatedClientSessionEntity clientSessionToImport = createAuthenticatedClientSessionInstance(clientSession,
|
||||
realmId, clientUUID, true);
|
||||
realmId, clientUUID);
|
||||
|
||||
// Update timestamp to the same value as userSession.
|
||||
// LastSessionRefresh of userSession from DB will have the correct value.
|
||||
clientSessionToImport.setTimestamp(lastSessionRefresh);
|
||||
|
||||
clientSessionsById.put(clientSessionToImport.getId(), new SessionEntityWrapper<>(clientSessionToImport));
|
||||
clientSessionsById.put(new EmbeddedClientSessionKey(userSessionId, clientUUID), new SessionEntityWrapper<>(clientSessionToImport));
|
||||
|
||||
// Update userSession entity with the clientSession
|
||||
clientSessions.put(clientUUID, clientSessionToImport.getId());
|
||||
clientSessions.add(clientUUID);
|
||||
}
|
||||
return clientSessionsById;
|
||||
}
|
||||
@ -372,19 +392,16 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, String clientSessionId, boolean offline) {
|
||||
if (clientSessionId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AuthenticatedClientSessionEntity clientSessionEntityFromCache = getClientSessionEntity(UUID.fromString(clientSessionId), offline);
|
||||
public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, boolean offline) {
|
||||
var key = new EmbeddedClientSessionKey(userSession.getId(), client.getId());
|
||||
AuthenticatedClientSessionEntity clientSessionEntityFromCache = getClientSessionEntity(key, offline);
|
||||
if (clientSessionEntityFromCache != null) {
|
||||
return wrap(userSession, client, clientSessionEntityFromCache, offline);
|
||||
return wrap(userSession, client, clientSessionEntityFromCache, key, offline);
|
||||
}
|
||||
|
||||
// offline client session lookup in the persister
|
||||
if (offline) {
|
||||
log.debugf("Offline client session is not found in cache, try to load from db, userSession [%s] clientSessionId [%s] clientId [%s]", userSession.getId(), clientSessionId, client.getClientId());
|
||||
log.debugf("Offline client session is not found in cache, try to load from db, %s", key);
|
||||
return getClientSessionEntityFromPersistenceProvider(userSession, client);
|
||||
}
|
||||
|
||||
@ -403,9 +420,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
getClientSessionTransaction(true), true);
|
||||
}
|
||||
|
||||
private AuthenticatedClientSessionEntity getClientSessionEntity(UUID id, boolean offline) {
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> tx = getClientSessionTransaction(offline);
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> entityWrapper = tx.get(id);
|
||||
private AuthenticatedClientSessionEntity getClientSessionEntity(EmbeddedClientSessionKey key, boolean offline) {
|
||||
var tx = getClientSessionTransaction(offline);
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> entityWrapper = tx.get(key);
|
||||
return entityWrapper == null ? null : entityWrapper.getEntity();
|
||||
}
|
||||
|
||||
@ -560,7 +577,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
FuturesHelper futures = new FuturesHelper();
|
||||
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(getCache(offline));
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> localClientSessionCache = CacheDecorators.localCache(getClientSessionCache(offline));
|
||||
var localClientSessionCache = CacheDecorators.localCache(getClientSessionCache(offline));
|
||||
|
||||
final AtomicInteger userSessionsSize = new AtomicInteger();
|
||||
|
||||
@ -574,8 +591,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
// Remove session from remoteCache too. Use removeAsync for better perf
|
||||
Future<?> future = localCache.removeAsync(userSessionEntity.getKey());
|
||||
futures.addTask(future);
|
||||
userSessionEntity.getValue().getEntity().getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
|
||||
Future<?> f = localClientSessionCache.removeAsync(clientSessionId);
|
||||
userSessionEntity.getValue().getEntity().getClientSessions().forEach(clientUUID -> {
|
||||
Future<?> f = localClientSessionCache.removeAsync(new EmbeddedClientSessionKey(userSessionEntity.getKey(), clientUUID));
|
||||
futures.addTask(f);
|
||||
});
|
||||
});
|
||||
@ -634,16 +651,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
}
|
||||
|
||||
protected void removeUserSession(UserSessionEntity sessionEntity, boolean offline) {
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
|
||||
sessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> clientSessionUpdateTx.addTask(clientSessionId, Tasks.removeSync()));
|
||||
SessionUpdateTask<UserSessionEntity> removeTask = Tasks.removeSync();
|
||||
userSessionUpdateTx.addTask(sessionEntity.getId(), removeTask);
|
||||
var clientSessionUpdateTx = getClientSessionTransaction(offline);
|
||||
sessionEntity.getClientSessions().forEach(clientUUID -> clientSessionUpdateTx.addTask(new EmbeddedClientSessionKey(sessionEntity.getId(), clientUUID), Tasks.removeSync()));
|
||||
getTransaction(offline).addTask(sessionEntity.getId(), Tasks.removeSync());
|
||||
}
|
||||
|
||||
UserSessionAdapter<InfinispanUserSessionProvider> wrap(RealmModel realm, UserSessionEntity entity, boolean offline, UserModel user) {
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
|
||||
var clientSessionUpdateTx = getClientSessionTransaction(offline);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
@ -678,9 +693,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
return wrap(realm, entity, offline, user);
|
||||
}
|
||||
|
||||
AuthenticatedClientSessionAdapter wrap(UserSessionModel userSession, ClientModel client, AuthenticatedClientSessionEntity entity, boolean offline) {
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
|
||||
return entity != null ? new AuthenticatedClientSessionAdapter(session, entity, client, userSession, clientSessionUpdateTx, offline) : null;
|
||||
AuthenticatedClientSessionAdapter wrap(UserSessionModel userSession, ClientModel client, AuthenticatedClientSessionEntity entity, EmbeddedClientSessionKey key, boolean offline) {
|
||||
InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
|
||||
return entity != null ? new AuthenticatedClientSessionAdapter(session, entity, client, userSession, clientSessionUpdateTx, key, offline) : null;
|
||||
}
|
||||
|
||||
UserSessionEntity getUserSessionEntity(RealmModel realm, UserSessionModel userSession, boolean offline) {
|
||||
@ -734,14 +749,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
getOfflineUserSession(offlineUserSession.getRealm(), offlineUserSession.getId());
|
||||
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(true);
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(true);
|
||||
var clientSessionUpdateTx = getClientSessionTransaction(true);
|
||||
AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession, userSessionUpdateTx, clientSessionUpdateTx, false);
|
||||
assert offlineClientSession != null; // no expiration checked, it is never null
|
||||
|
||||
// update timestamp to current time
|
||||
offlineClientSession.setTimestamp(Time.currentTime());
|
||||
offlineClientSession.getNotes().put(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(offlineClientSession.getTimestamp()));
|
||||
offlineClientSession.getNotes().put(AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE, String.valueOf(offlineUserSession.getStarted()));
|
||||
offlineClientSession.setNote(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(offlineClientSession.getTimestamp()));
|
||||
offlineClientSession.setNote(AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE, String.valueOf(offlineUserSession.getStarted()));
|
||||
|
||||
session.getProvider(UserSessionPersisterProvider.class).createClientSession(clientSession, true);
|
||||
|
||||
@ -777,27 +792,28 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
return;
|
||||
}
|
||||
|
||||
Map<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsById = new HashMap<>();
|
||||
Map<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsById = new HashMap<>();
|
||||
|
||||
Map<String, SessionEntityWrapper<UserSessionEntity>> sessionsById = persistentUserSessions.stream()
|
||||
.map((UserSessionModel persistentUserSession) -> {
|
||||
|
||||
UserSessionEntity userSessionEntityToImport = UserSessionEntity.createFromModel(persistentUserSession);
|
||||
Set<String> clientSessions = userSessionEntityToImport.getClientSessions();
|
||||
String userSessionId = userSessionEntityToImport.getId();
|
||||
|
||||
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : persistentUserSession.getAuthenticatedClientSessions().entrySet()) {
|
||||
String clientUUID = entry.getKey();
|
||||
AuthenticatedClientSessionModel clientSession = entry.getValue();
|
||||
AuthenticatedClientSessionEntity clientSessionToImport = createAuthenticatedClientSessionInstance(clientSession,
|
||||
userSessionEntityToImport.getRealmId(), clientUUID, offline);
|
||||
userSessionEntityToImport.getRealmId(), clientUUID);
|
||||
|
||||
// Update timestamp to same value as userSession. LastSessionRefresh of userSession from DB will have correct value
|
||||
clientSessionToImport.setTimestamp(userSessionEntityToImport.getLastSessionRefresh());
|
||||
|
||||
clientSessionsById.put(clientSessionToImport.getId(), new SessionEntityWrapper<>(clientSessionToImport));
|
||||
clientSessionsById.put(new EmbeddedClientSessionKey(userSessionId, clientUUID), new SessionEntityWrapper<>(clientSessionToImport));
|
||||
|
||||
// Update userSession entity with the clientSession
|
||||
AuthenticatedClientSessionStore clientSessions = userSessionEntityToImport.getAuthenticatedClientSessions();
|
||||
clientSessions.put(clientUUID, clientSessionToImport.getId());
|
||||
clientSessions.add(clientUUID);
|
||||
}
|
||||
|
||||
return userSessionEntityToImport;
|
||||
@ -818,7 +834,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
}
|
||||
|
||||
// Import client sessions
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessCache = getClientSessionCache(offline);
|
||||
var clientSessCache = getClientSessionCache(offline);
|
||||
|
||||
if (importWithExpiration) {
|
||||
importSessionsWithExpiration(clientSessionsById, clientSessCache,
|
||||
@ -862,10 +878,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
|
||||
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter<?> sessionToImportInto, AuthenticatedClientSessionModel clientSession,
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx,
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx,
|
||||
InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionUpdateTx,
|
||||
boolean checkExpiration) {
|
||||
AuthenticatedClientSessionEntity entity = createAuthenticatedClientSessionInstance(clientSession,
|
||||
sessionToImportInto.getRealm().getId(), clientSession.getClient().getId(), true);
|
||||
sessionToImportInto.getRealm().getId(), clientSession.getClient().getId());
|
||||
|
||||
// Update timestamp to same value as userSession. LastSessionRefresh of userSession from DB will have correct value
|
||||
entity.setTimestamp(sessionToImportInto.getLastSessionRefresh());
|
||||
@ -877,26 +893,27 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
}
|
||||
}
|
||||
|
||||
final UUID clientSessionId = entity.getId();
|
||||
String clientUUID = clientSession.getClient().getId();
|
||||
String userSessionId = sessionToImportInto.getId();
|
||||
|
||||
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
|
||||
clientSessionUpdateTx.addTask(entity.getId(), createClientSessionTask, entity, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||
|
||||
AuthenticatedClientSessionStore clientSessions = sessionToImportInto.getEntity().getAuthenticatedClientSessions();
|
||||
clientSessions.put(clientSession.getClient().getId(), clientSessionId);
|
||||
var key = new EmbeddedClientSessionKey(userSessionId, clientUUID);
|
||||
clientSessionUpdateTx.addTask(key, Tasks.addIfAbsentSync(), entity, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||
|
||||
userSessionUpdateTx.addTask(sessionToImportInto.getId(), new RegisterClientSessionTask(clientSession.getClient().getId(), clientSessionId));
|
||||
sessionToImportInto.getEntity().getClientSessions().add(clientUUID);
|
||||
|
||||
return new AuthenticatedClientSessionAdapter(session, entity, clientSession.getClient(), sessionToImportInto, clientSessionUpdateTx, true);
|
||||
userSessionUpdateTx.addTask(sessionToImportInto.getId(), new RegisterClientSessionTask(clientUUID));
|
||||
|
||||
return new AuthenticatedClientSessionAdapter(session, entity, clientSession.getClient(), sessionToImportInto, clientSessionUpdateTx, key, true);
|
||||
}
|
||||
|
||||
|
||||
private AuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(AuthenticatedClientSessionModel clientSession,
|
||||
String realmId, String clientId, boolean offline) {
|
||||
final UUID clientSessionId = keyGenerator.generateKeyUUID(session, getClientSessionCache(offline));
|
||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
||||
String realmId, String clientId) {
|
||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
|
||||
entity.setRealmId(realmId);
|
||||
entity.setClientId(clientId);
|
||||
entity.setUserSessionId(clientSession.getUserSession().getId());
|
||||
|
||||
entity.setAction(clientSession.getAction());
|
||||
entity.setAuthMethod(clientSession.getProtocol());
|
||||
@ -908,13 +925,12 @@ public class InfinispanUserSessionProvider implements UserSessionProvider, Sessi
|
||||
return entity;
|
||||
}
|
||||
|
||||
private record RegisterClientSessionTask(String clientUuid, UUID clientSessionId)
|
||||
private record RegisterClientSessionTask(String clientUuid)
|
||||
implements SessionUpdateTask<UserSessionEntity> {
|
||||
|
||||
@Override
|
||||
public void runUpdate(UserSessionEntity session) {
|
||||
AuthenticatedClientSessionStore clientSessions = session.getAuthenticatedClientSessions();
|
||||
clientSessions.put(clientUuid, clientSessionId);
|
||||
session.getClientSessions().add(clientUuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -21,7 +21,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -50,6 +49,7 @@ import org.keycloak.models.sessions.infinispan.changes.UserSessionPersistentChan
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStore;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStoreFactory;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.EmbeddedClientSessionKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.events.AbstractUserSessionClusterListener;
|
||||
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
||||
@ -89,8 +89,8 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
|
||||
private CacheHolder<String, UserSessionEntity> sessionCacheHolder;
|
||||
private CacheHolder<String, UserSessionEntity> offlineSessionCacheHolder;
|
||||
private CacheHolder<UUID, AuthenticatedClientSessionEntity> clientSessionCacheHolder;
|
||||
private CacheHolder<UUID, AuthenticatedClientSessionEntity> offlineClientSessionCacheHolder;
|
||||
private CacheHolder<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionCacheHolder;
|
||||
private CacheHolder<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> offlineClientSessionCacheHolder;
|
||||
|
||||
private long offlineSessionCacheEntryLifespanOverride;
|
||||
|
||||
@ -410,8 +410,8 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
|
||||
private record VolatileTransactions(InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx,
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx,
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionTx,
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> offlineClientSessionTx) {}
|
||||
InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionTx,
|
||||
InfinispanChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> offlineClientSessionTx) {}
|
||||
|
||||
private record PersistentTransaction(UserSessionPersistentChangelogBasedTransaction userTx, ClientSessionPersistentChangelogBasedTransaction clientTx) {}
|
||||
|
||||
|
||||
@ -17,13 +17,11 @@
|
||||
|
||||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -37,6 +35,7 @@ import io.reactivex.rxjava3.core.Flowable;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.client.hotrod.RemoteCache;
|
||||
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
|
||||
import org.infinispan.commons.api.AsyncCache;
|
||||
import org.infinispan.commons.api.BasicCache;
|
||||
import org.infinispan.commons.util.ByRef;
|
||||
import org.infinispan.commons.util.concurrent.CompletionStages;
|
||||
@ -72,7 +71,7 @@ import org.keycloak.models.sessions.infinispan.changes.Tasks;
|
||||
import org.keycloak.models.sessions.infinispan.changes.UserSessionPersistentChangelogBasedTransaction;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.EmbeddedClientSessionKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
||||
@ -128,7 +127,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
return sessionTx.getCache(offline);
|
||||
}
|
||||
|
||||
protected Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> getClientSessionCache(boolean offline) {
|
||||
protected Cache<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> getClientSessionCache(boolean offline) {
|
||||
return clientSessionTx.getCache(offline);
|
||||
}
|
||||
|
||||
@ -144,8 +143,8 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
|
||||
@Override
|
||||
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
||||
final UUID clientSessionId = PersistentUserSessionProvider.createClientSessionUUID(userSession.getId(), client.getId());
|
||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
||||
final EmbeddedClientSessionKey cacheKey = new EmbeddedClientSessionKey(userSession.getId(), client.getId());
|
||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
|
||||
entity.setRealmId(realm.getId());
|
||||
entity.setClientId(client.getId());
|
||||
entity.setUserSessionId(userSession.getId());
|
||||
@ -156,7 +155,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
entity.getNotes().put(AuthenticatedClientSessionModel.USER_SESSION_REMEMBER_ME_NOTE, "true");
|
||||
}
|
||||
|
||||
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(session, entity, client, userSession, clientSessionTx, false);
|
||||
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(session, entity, client, userSession, clientSessionTx, cacheKey, false);
|
||||
|
||||
if (userSession.isOffline()) {
|
||||
// If this is an offline session, and the referred online session doesn't exist anymore, don't register the client session in the transaction.
|
||||
@ -171,8 +170,8 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
userSession.getPersistenceState() : UserSessionModel.SessionPersistenceState.PERSISTENT;
|
||||
|
||||
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
|
||||
clientSessionTx.addTask(clientSessionId, createClientSessionTask, entity, persistenceState);
|
||||
sessionTx.registerClientSession(userSession.getId(), client.getId(), clientSessionId, userSession.isOffline());
|
||||
clientSessionTx.addTask(cacheKey, createClientSessionTask, entity, persistenceState);
|
||||
sessionTx.registerClientSession(userSession.getId(), client.getId(), userSession.isOffline());
|
||||
|
||||
return adapter;
|
||||
}
|
||||
@ -298,18 +297,11 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, String clientSessionId, boolean offline) {
|
||||
if (clientSessionId == null) {
|
||||
log.debugf("Client-session id is null. userSessionId=%s, clientId=%s, offline=%s",
|
||||
userSession.getId(), client.getId(), offline);
|
||||
return null;
|
||||
}
|
||||
|
||||
UUID clientSessionUUID = UUID.fromString(clientSessionId);
|
||||
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> clientSessionEntity = clientSessionTx.get(client.getRealm(), client, userSession, clientSessionUUID, offline);
|
||||
public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, boolean offline) {
|
||||
var key = new EmbeddedClientSessionKey(userSession.getId(), client.getId());
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> clientSessionEntity = clientSessionTx.get(client.getRealm(), client, userSession, key, offline);
|
||||
if (clientSessionEntity != null) {
|
||||
return new AuthenticatedClientSessionAdapter(session, clientSessionEntity.getEntity(), client, userSession, clientSessionTx, offline);
|
||||
return new AuthenticatedClientSessionAdapter(session, clientSessionEntity.getEntity(), client, userSession, clientSessionTx, key, offline);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -434,15 +426,14 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
// public for usage in the testsuite
|
||||
public void removeLocalUserSessions(String realmId, boolean offline) {
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(getCache(offline));
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = getClientSessionCache(offline);
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> localClientSessionCache = CacheDecorators.localCache(clientSessionCache);
|
||||
var localClientSessionCache = CacheDecorators.localCache(getClientSessionCache(offline));
|
||||
final AtomicInteger userSessionsSize = new AtomicInteger();
|
||||
|
||||
removeEntriesByRealm(realmId, localCache, userSessionsSize, localClientSessionCache);
|
||||
log.debugf("Removed %d sessions in realm %s. Offline: %b", (Object) userSessionsSize.get(), realmId, offline);
|
||||
}
|
||||
|
||||
private static void removeEntriesByRealm(String realmId, Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionsCache, AtomicInteger userSessionsSize, Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessions) {
|
||||
private static void removeEntriesByRealm(String realmId, Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionsCache, AtomicInteger userSessionsSize, Cache<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessions) {
|
||||
FuturesHelper futures = new FuturesHelper();
|
||||
|
||||
sessionsCache
|
||||
@ -456,8 +447,8 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
// Remove session from remoteCache too. Use removeAsync for better perf
|
||||
Future<SessionEntityWrapper<UserSessionEntity>> future = sessionsCache.removeAsync(userSessionEntity.getId());
|
||||
futures.addTask(future);
|
||||
userSessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
|
||||
Future<SessionEntityWrapper<AuthenticatedClientSessionEntity>> f = clientSessions.removeAsync(clientSessionId);
|
||||
userSessionEntity.getClientSessions().forEach(clientUUID -> {
|
||||
Future<SessionEntityWrapper<AuthenticatedClientSessionEntity>> f = clientSessions.removeAsync(new EmbeddedClientSessionKey(userSessionEntity.getId(), clientUUID));
|
||||
futures.addTask(f);
|
||||
});
|
||||
});
|
||||
@ -512,7 +503,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
}
|
||||
|
||||
protected void removeUserSession(UserSessionEntity sessionEntity, boolean offline) {
|
||||
sessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> clientSessionTx.addTask(clientSessionId, Tasks.removeSync(offline)));
|
||||
sessionEntity.getClientSessions().forEach(clientUUID -> clientSessionTx.addTask(new EmbeddedClientSessionKey(sessionEntity.getId(), clientUUID), Tasks.removeSync(offline)));
|
||||
SessionUpdateTask<UserSessionEntity> removeTask = Tasks.removeSync(offline);
|
||||
sessionTx.addTask(sessionEntity.getId(), removeTask);
|
||||
}
|
||||
@ -607,8 +598,8 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
|
||||
// update timestamp to current time
|
||||
offlineClientSession.setTimestamp(Time.currentTime());
|
||||
offlineClientSession.getNotes().put(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(offlineClientSession.getTimestamp()));
|
||||
offlineClientSession.getNotes().put(AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE, String.valueOf(offlineUserSession.getStarted()));
|
||||
offlineClientSession.setNote(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(offlineClientSession.getTimestamp()));
|
||||
offlineClientSession.setNote(AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE, String.valueOf(offlineUserSession.getStarted()));
|
||||
|
||||
return offlineClientSession;
|
||||
}
|
||||
@ -662,7 +653,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsById = new HashMap<>();
|
||||
Map<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsById = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : persistentUserSession.getAuthenticatedClientSessions().entrySet()) {
|
||||
String clientUUID = entry.getKey();
|
||||
@ -678,11 +669,10 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
clientSessionToImport.setTimestamp(userSessionEntityToImport.getLastSessionRefresh());
|
||||
}
|
||||
|
||||
clientSessionsById.put(clientSessionToImport.getId(), new SessionEntityWrapper<>(clientSessionToImport));
|
||||
clientSessionsById.put(new EmbeddedClientSessionKey(persistentUserSession.getId(), clientUUID), new SessionEntityWrapper<>(clientSessionToImport));
|
||||
|
||||
// Update userSession entity with the clientSession
|
||||
AuthenticatedClientSessionStore clientSessions = userSessionEntityToImport.getAuthenticatedClientSessions();
|
||||
clientSessions.put(clientUUID, clientSessionToImport.getId());
|
||||
userSessionEntityToImport.getClientSessions().add(clientUUID);
|
||||
}
|
||||
|
||||
SessionEntityWrapper<UserSessionEntity> wrappedUserSessionEntity = new SessionEntityWrapper<>(userSessionEntityToImport);
|
||||
@ -750,7 +740,6 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
entity.setBrokerUserId(userSession.getBrokerUserId());
|
||||
entity.setIpAddress(userSession.getIpAddress());
|
||||
entity.setNotes(userSession.getNotes() == null ? new ConcurrentHashMap<>() : userSession.getNotes());
|
||||
entity.setAuthenticatedClientSessions(new AuthenticatedClientSessionStore());
|
||||
entity.setRememberMe(userSession.isRememberMe());
|
||||
entity.setState(userSession.getState());
|
||||
if (userSession instanceof OfflineUserSessionModel offlineUserSession) {
|
||||
@ -782,16 +771,15 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
// Update timestamp to same value as userSession. LastSessionRefresh of userSession from DB will have correct value
|
||||
entity.setTimestamp(sessionToImportInto.getLastSessionRefresh());
|
||||
|
||||
final UUID clientSessionId = entity.getId();
|
||||
var clientUUID = clientSession.getClient().getId();
|
||||
|
||||
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
|
||||
clientSessionTx.addTask(entity.getId(), createClientSessionTask, entity, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||
var key = new EmbeddedClientSessionKey(sessionToImportInto.getId(), clientUUID);
|
||||
clientSessionTx.addTask(key, Tasks.addIfAbsentSync(), entity, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||
|
||||
AuthenticatedClientSessionStore clientSessions = sessionToImportInto.getEntity().getAuthenticatedClientSessions();
|
||||
clientSessions.put(clientSession.getClient().getId(), clientSessionId);
|
||||
sessionTx.registerClientSession(sessionToImportInto.getId(), clientSession.getClient().getId(), clientSessionId, true);
|
||||
sessionToImportInto.getEntity().getClientSessions().add(clientUUID);
|
||||
sessionTx.registerClientSession(sessionToImportInto.getId(), clientUUID, true);
|
||||
|
||||
return new AuthenticatedClientSessionAdapter(session, entity, clientSession.getClient(), sessionToImportInto, clientSessionTx, true);
|
||||
return new AuthenticatedClientSessionAdapter(session, entity, clientSession.getClient(), sessionToImportInto, clientSessionTx, key, true);
|
||||
}
|
||||
|
||||
public SessionEntityWrapper<UserSessionEntity> wrapPersistentEntity(RealmModel realm, boolean offline, UserSessionModel persistentUserSession) {
|
||||
@ -821,10 +809,11 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = new EmbeddedClientSessionKey(userSessionEntity.getId(), clientUUID);
|
||||
|
||||
// Update userSession entity with the clientSession
|
||||
AuthenticatedClientSessionStore clientSessions = userSessionEntity.getAuthenticatedClientSessions();
|
||||
clientSessions.put(clientUUID, clientSession.getId());
|
||||
clientSessionTx.addTask(clientSession.getId(), null, clientSession, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||
userSessionEntity.getClientSessions().add(key.clientId());
|
||||
clientSessionTx.addTask(key, null, clientSession, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||
}
|
||||
|
||||
return sessionTx.get(userSessionEntity.getId(), offline);
|
||||
@ -843,11 +832,6 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
return idleChecker.apply(realm, null, entity) == SessionTimeouts.ENTRY_EXPIRED_FLAG || lifetimeChecker.apply(realm, null, entity) == SessionTimeouts.ENTRY_EXPIRED_FLAG;
|
||||
}
|
||||
|
||||
public static UUID createClientSessionUUID(String userSessionId, String clientId) {
|
||||
// This allows creating a UUID that is constant even if the entry is reloaded from the database
|
||||
return UUID.nameUUIDFromBytes((userSessionId + clientId).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(String modelVersion) {
|
||||
// Changed encoding from JBoss Marshalling to ProtoStream.
|
||||
@ -855,6 +839,24 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
if ("26.0.0".equals(modelVersion)) {
|
||||
log.debug("Clear caches to migrate to Infinispan Protostream");
|
||||
CompletionStages.join(session.getProvider(InfinispanConnectionProvider.class).migrateToProtoStream());
|
||||
} else if ("26.4.0".equals(modelVersion)) {
|
||||
log.debug("Clear caches as client session entries are now outdated and are not migrated");
|
||||
// This is a best-effort approach: Even if due to a rolling update some entries are left there, the checking of sessions and tokens does not depend on them.
|
||||
// Refreshing of tokens will still work even if the user session does not contain the list of client sessions.
|
||||
var stage = CompletionStages.aggregateCompletionStage();
|
||||
Stream.of(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME)
|
||||
.map(s -> {
|
||||
InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
|
||||
if (provider != null) {
|
||||
return provider.getCache(s, false);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.map(AsyncCache::clearAsync)
|
||||
.forEach(stage::dependsOn);
|
||||
CompletionStages.join(stage.freeze());
|
||||
}
|
||||
}
|
||||
|
||||
@ -863,11 +865,12 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
* This method is public so people can use it to build their custom migrations or re-import sessions when necessary
|
||||
* in a future version of Keycloak.
|
||||
*/
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
public void migrateNonPersistentSessionsToPersistentSessions() {
|
||||
var sessionCache = sessionTx.getCache(false);
|
||||
var clientSessionCache = clientSessionTx.getCache(false);
|
||||
JpaChangesPerformer<String, UserSessionEntity> userSessionPerformer = new JpaChangesPerformer<>(sessionCache.getName(), null);
|
||||
JpaChangesPerformer<UUID, AuthenticatedClientSessionEntity> clientSessionPerformer = new JpaChangesPerformer<>(clientSessionCache.getName(), null);
|
||||
JpaChangesPerformer<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionPerformer = new JpaChangesPerformer<>(clientSessionCache.getName(), null);
|
||||
AtomicInteger currentBatch = new AtomicInteger(0);
|
||||
var persistence = ComponentRegistry.componentOf(sessionCache, PersistenceManager.class);
|
||||
if (persistence != null && !persistence.getStoresAsString().isEmpty()) {
|
||||
@ -896,23 +899,25 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
* Such entries should first be cleared from the cache before this is being called.
|
||||
* As this is assumed to run once during the upgrade to Keycloak 25, this should be safe to assume.
|
||||
*/
|
||||
private void processEntryFromCache(SessionEntityWrapper<UserSessionEntity> sessionEntityWrapper, JpaChangesPerformer<String, UserSessionEntity> userSessionPerformer, JpaChangesPerformer<UUID, AuthenticatedClientSessionEntity> clientSessionPerformer, AtomicInteger count) {
|
||||
private void processEntryFromCache(SessionEntityWrapper<UserSessionEntity> sessionEntityWrapper, JpaChangesPerformer<String, UserSessionEntity> userSessionPerformer, JpaChangesPerformer<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionPerformer, AtomicInteger count) {
|
||||
RealmModel realm = session.realms().getRealm(sessionEntityWrapper.getEntity().getRealmId());
|
||||
if (realm == null) {
|
||||
// ignoring old and unknown realm found in the session
|
||||
return;
|
||||
}
|
||||
var clientSessionCache = clientSessionTx.getCache(false);
|
||||
sessionEntityWrapper.getEntity().getAuthenticatedClientSessions().forEach((clientId, uuid) -> {
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> clientSession = clientSessionCache.get(uuid);
|
||||
sessionEntityWrapper.getEntity().getClientSessions().forEach(clientId-> {
|
||||
var key = new EmbeddedClientSessionKey(sessionEntityWrapper.getEntity().getId(), clientId);
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> clientSession = clientSessionCache.get(key);
|
||||
if (clientSession != null) {
|
||||
// TODO [pruivo] [KC27] Remove!
|
||||
// This is necessary because client sessions created by a KC version < 22 do not have clientId set within the entity.
|
||||
if (clientSession.getEntity().getClientId() == null) {
|
||||
clientSession.getEntity().setClientId(clientId);
|
||||
}
|
||||
clientSession.getEntity().setUserSessionId(sessionEntityWrapper.getEntity().getId());
|
||||
MergedUpdate<AuthenticatedClientSessionEntity> merged = MergedUpdate.computeUpdate(Collections.singletonList(Tasks.addIfAbsentSync()), clientSession, 1, 1);
|
||||
clientSessionPerformer.registerChange(Map.entry(uuid, new SessionUpdatesList<>(realm, clientSession)), merged);
|
||||
clientSessionPerformer.registerChange(Map.entry(key, new SessionUpdatesList<>(realm, clientSession)), merged);
|
||||
}
|
||||
});
|
||||
MergedUpdate<UserSessionEntity> merged = MergedUpdate.computeUpdate(Collections.singletonList(Tasks.addIfAbsentSync()), sessionEntityWrapper, 1, 1);
|
||||
@ -925,7 +930,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
}
|
||||
}
|
||||
|
||||
private <E extends SessionEntity, K> void flush(JpaChangesPerformer<K, E> userSessionsPerformer, JpaChangesPerformer<UUID, AuthenticatedClientSessionEntity> clientSessionPerformer) {
|
||||
private <E extends SessionEntity, K> void flush(JpaChangesPerformer<K, E> userSessionsPerformer, JpaChangesPerformer<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionPerformer) {
|
||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(),
|
||||
s -> {
|
||||
userSessionsPerformer.write(s);
|
||||
|
||||
@ -30,7 +30,7 @@ import org.keycloak.models.sessions.infinispan.changes.SessionsChangelogBasedTra
|
||||
import org.keycloak.models.sessions.infinispan.changes.Tasks;
|
||||
import org.keycloak.models.sessions.infinispan.changes.UserSessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.EmbeddedClientSessionKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -39,9 +39,6 @@ import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
@ -56,7 +53,7 @@ public class UserSessionAdapter<T extends SessionRefreshStore & UserSessionProvi
|
||||
|
||||
private final SessionsChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx;
|
||||
|
||||
private final SessionsChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx;
|
||||
private final SessionsChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionUpdateTx;
|
||||
|
||||
private final RealmModel realm;
|
||||
|
||||
@ -70,7 +67,7 @@ public class UserSessionAdapter<T extends SessionRefreshStore & UserSessionProvi
|
||||
|
||||
public UserSessionAdapter(KeycloakSession session, UserModel user, T provider,
|
||||
SessionsChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx,
|
||||
SessionsChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx,
|
||||
SessionsChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> clientSessionUpdateTx,
|
||||
RealmModel realm, UserSessionEntity entity, boolean offline) {
|
||||
this.session = session;
|
||||
this.user = user;
|
||||
@ -84,30 +81,28 @@ public class UserSessionAdapter<T extends SessionRefreshStore & UserSessionProvi
|
||||
|
||||
@Override
|
||||
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
|
||||
AuthenticatedClientSessionStore clientSessionEntities = entity.getAuthenticatedClientSessions();
|
||||
var clientSessionEntities = entity.getClientSessions();
|
||||
Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
|
||||
|
||||
List<String> removedClientUUIDS = new LinkedList<>();
|
||||
|
||||
if (clientSessionEntities != null) {
|
||||
clientSessionEntities.forEach((String key, UUID value) -> {
|
||||
// Check if client still exists
|
||||
ClientModel client = realm.getClientById(key);
|
||||
if (client != null) {
|
||||
final AuthenticatedClientSessionModel clientSession = provider.getClientSession(this, client, value.toString(), offline);
|
||||
if (clientSession != null) {
|
||||
result.put(key, clientSession);
|
||||
} else {
|
||||
// Either the client session has expired, or it hasn't been added by a concurrently running login yet.
|
||||
// So it is unsafe to clear it, so we need to keep it for now. Otherwise, the test ConcurrentLoginTest.concurrentLoginSingleUser will fail.
|
||||
// removedClientUUIDS.add(key);
|
||||
}
|
||||
} else {
|
||||
// client does no longer exist
|
||||
removedClientUUIDS.add(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
clientSessionEntities.forEach(clientUUID -> {
|
||||
// Check if client still exists
|
||||
ClientModel client = realm.getClientById(clientUUID);
|
||||
if (client == null) {
|
||||
// client does no longer exist
|
||||
removedClientUUIDS.add(clientUUID);
|
||||
return;
|
||||
}
|
||||
var clientSession = provider.getClientSession(this, client, offline);
|
||||
if (clientSession == null) {
|
||||
// Either the client session has expired, or it hasn't been added by a concurrently running login yet.
|
||||
// So it is unsafe to remove it, so we need to keep it for now.
|
||||
// Otherwise, the test ConcurrentLoginTest.concurrentLoginSingleUser will fail.
|
||||
return;
|
||||
}
|
||||
result.put(clientUUID, clientSession);
|
||||
});
|
||||
|
||||
removeAuthenticatedClientSessions(removedClientUUIDS);
|
||||
|
||||
@ -116,25 +111,16 @@ public class UserSessionAdapter<T extends SessionRefreshStore & UserSessionProvi
|
||||
|
||||
@Override
|
||||
public AuthenticatedClientSessionModel getAuthenticatedClientSessionByClient(String clientUUID) {
|
||||
AuthenticatedClientSessionStore clientSessionEntities = entity.getAuthenticatedClientSessions();
|
||||
final UUID clientSessionId = clientSessionEntities.get(clientUUID);
|
||||
|
||||
if (clientSessionId == null) {
|
||||
logger.debugf("Client to client session mapping not found. userSessionId=%s, clientId=%s, offline=%s, mappings=%s",
|
||||
getId(), clientUUID, offline, clientSessionEntities);
|
||||
return null;
|
||||
}
|
||||
|
||||
ClientModel client = realm.getClientById(clientUUID);
|
||||
|
||||
if (client != null) {
|
||||
// Might return null either the client session has expired, or it hasn't been added by a concurrently running login yet.
|
||||
// So it is unsafe to clear it, so we need to keep it for now. Otherwise, the test ConcurrentLoginTest.concurrentLoginSingleUser will fail.
|
||||
return provider.getClientSession(this, client, clientSessionId.toString(), offline);
|
||||
return provider.getClientSession(this, client, offline);
|
||||
}
|
||||
|
||||
logger.debugf("Client not found. Removing from mappings. userSessionId=%s, clientId=%s, clientSessionId=%s, offline=%s",
|
||||
getId(), clientUUID, clientSessionId, offline);
|
||||
getId(), clientUUID, new EmbeddedClientSessionKey(getId(), clientUUID), offline);
|
||||
removeAuthenticatedClientSessions(Collections.singleton(clientUUID));
|
||||
return null;
|
||||
}
|
||||
@ -148,16 +134,15 @@ public class UserSessionAdapter<T extends SessionRefreshStore & UserSessionProvi
|
||||
|
||||
// do not iterate the removedClientUUIDS and remove the clientSession directly as the addTask can manipulate
|
||||
// the collection being iterated, and that can lead to unpredictable behaviour (e.g. NPE)
|
||||
List<UUID> clientSessionUuids = removedClientUUIDS.stream()
|
||||
.map(entity.getAuthenticatedClientSessions()::get)
|
||||
.filter(Objects::nonNull)
|
||||
List<String> clientSessionUuids = removedClientUUIDS.stream()
|
||||
.filter(entity.getClientSessions()::contains)
|
||||
.toList();
|
||||
|
||||
// Update user session
|
||||
UserSessionUpdateTask task = new UserSessionUpdateTask() {
|
||||
@Override
|
||||
public void runUpdate(UserSessionEntity entity) {
|
||||
removedClientUUIDS.forEach(entity.getAuthenticatedClientSessions()::remove);
|
||||
removedClientUUIDS.forEach(entity.getClientSessions()::remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -167,7 +152,7 @@ public class UserSessionAdapter<T extends SessionRefreshStore & UserSessionProvi
|
||||
};
|
||||
update(task);
|
||||
|
||||
clientSessionUuids.forEach(clientSessionId -> this.clientSessionUpdateTx.addTask(clientSessionId, Tasks.removeSync(offline)));
|
||||
clientSessionUuids.forEach(clientUUID -> this.clientSessionUpdateTx.addTask(new EmbeddedClientSessionKey(entity.getId(), clientUUID), Tasks.removeSync(offline)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -372,7 +357,7 @@ public class UserSessionAdapter<T extends SessionRefreshStore & UserSessionProvi
|
||||
|
||||
entity.setState(null);
|
||||
entity.getNotes().clear();
|
||||
entity.getAuthenticatedClientSessions().clear();
|
||||
entity.getClientSessions().clear();
|
||||
}
|
||||
|
||||
};
|
||||
@ -385,11 +370,10 @@ public class UserSessionAdapter<T extends SessionRefreshStore & UserSessionProvi
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || !(o instanceof UserSessionModel)) {
|
||||
if (!(o instanceof UserSessionModel that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserSessionModel that = (UserSessionModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
|
||||
@ -25,10 +25,9 @@ import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.PersistentUserSessionProvider;
|
||||
import org.keycloak.models.sessions.infinispan.UserSessionAdapter;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.EmbeddedClientSessionKey;
|
||||
import org.keycloak.models.sessions.infinispan.util.SessionTimeouts;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -39,21 +38,21 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||
|
||||
public class ClientSessionPersistentChangelogBasedTransaction extends PersistentSessionsChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> {
|
||||
public class ClientSessionPersistentChangelogBasedTransaction extends PersistentSessionsChangelogBasedTransaction<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ClientSessionPersistentChangelogBasedTransaction.class);
|
||||
private final UserSessionPersistentChangelogBasedTransaction userSessionTx;
|
||||
|
||||
public ClientSessionPersistentChangelogBasedTransaction(KeycloakSession session,
|
||||
ArrayBlockingQueue<PersistentUpdate> batchingQueue,
|
||||
CacheHolder<UUID, AuthenticatedClientSessionEntity> cacheHolder,
|
||||
CacheHolder<UUID, AuthenticatedClientSessionEntity> offlineCacheHolder,
|
||||
CacheHolder<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> cacheHolder,
|
||||
CacheHolder<EmbeddedClientSessionKey, AuthenticatedClientSessionEntity> offlineCacheHolder,
|
||||
UserSessionPersistentChangelogBasedTransaction userSessionTx) {
|
||||
super(session, CLIENT_SESSION_CACHE_NAME, batchingQueue, cacheHolder, offlineCacheHolder);
|
||||
this.userSessionTx = userSessionTx;
|
||||
}
|
||||
|
||||
public void setUserSessionId(Collection<UUID> keys, String userSessionId, boolean offline) {
|
||||
public void setUserSessionId(Collection<EmbeddedClientSessionKey> keys, String userSessionId, boolean offline) {
|
||||
keys.stream().map(getUpdates(offline)::get)
|
||||
.filter(Objects::nonNull)
|
||||
.map(SessionUpdatesList::getEntityWrapper)
|
||||
@ -62,11 +61,14 @@ public class ClientSessionPersistentChangelogBasedTransaction extends Persistent
|
||||
.forEach(authenticatedClientSessionEntity -> authenticatedClientSessionEntity.setUserSessionId(userSessionId));
|
||||
}
|
||||
|
||||
public SessionEntityWrapper<AuthenticatedClientSessionEntity> get(RealmModel realm, ClientModel client, UserSessionModel userSession, UUID key, boolean offline) {
|
||||
public SessionEntityWrapper<AuthenticatedClientSessionEntity> get(RealmModel realm, ClientModel client, UserSessionModel userSession, EmbeddedClientSessionKey key, boolean offline) {
|
||||
if (key == null) {
|
||||
key = new EmbeddedClientSessionKey(userSession.getId(), client.getId());
|
||||
}
|
||||
SessionUpdatesList<AuthenticatedClientSessionEntity> myUpdates = getUpdates(offline).get(key);
|
||||
if (myUpdates == null) {
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> wrappedEntity = null;
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> cache = getCache(offline);
|
||||
Cache<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> cache = getCache(offline);
|
||||
if (cache != null) {
|
||||
wrappedEntity = cache.get(key);
|
||||
}
|
||||
@ -116,7 +118,7 @@ public class ClientSessionPersistentChangelogBasedTransaction extends Persistent
|
||||
}
|
||||
}
|
||||
|
||||
private SessionEntityWrapper<AuthenticatedClientSessionEntity> getSessionEntityFromPersister(RealmModel realm, ClientModel client, UserSessionModel userSession, UUID clientSessionId, boolean offline) {
|
||||
private SessionEntityWrapper<AuthenticatedClientSessionEntity> getSessionEntityFromPersister(RealmModel realm, ClientModel client, UserSessionModel userSession, EmbeddedClientSessionKey clientSessionId, boolean offline) {
|
||||
UserSessionPersisterProvider persister = kcSession.getProvider(UserSessionPersisterProvider.class);
|
||||
AuthenticatedClientSessionModel clientSession = persister.loadClientSession(realm, client, userSession, offline);
|
||||
|
||||
@ -137,9 +139,8 @@ public class ClientSessionPersistentChangelogBasedTransaction extends Persistent
|
||||
|
||||
public static AuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(String userSessionId, AuthenticatedClientSessionModel clientSession,
|
||||
String realmId, String clientId, boolean offline) {
|
||||
UUID clientSessionId = PersistentUserSessionProvider.createClientSessionUUID(userSessionId, clientId);
|
||||
|
||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
|
||||
entity.setRealmId(realmId);
|
||||
|
||||
entity.setAction(clientSession.getAction());
|
||||
@ -155,7 +156,7 @@ public class ClientSessionPersistentChangelogBasedTransaction extends Persistent
|
||||
return entity;
|
||||
}
|
||||
|
||||
private SessionEntityWrapper<AuthenticatedClientSessionEntity> importClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, AuthenticatedClientSessionModel persistentClientSession, UUID clientSessionId) {
|
||||
private SessionEntityWrapper<AuthenticatedClientSessionEntity> importClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, AuthenticatedClientSessionModel persistentClientSession, EmbeddedClientSessionKey clientSessionId) {
|
||||
AuthenticatedClientSessionEntity entity = createAuthenticatedClientSessionInstance(userSession.getId(), persistentClientSession,
|
||||
realm.getId(), client.getId(), userSession.isOffline());
|
||||
boolean offline = userSession.isOffline();
|
||||
@ -194,11 +195,8 @@ public class ClientSessionPersistentChangelogBasedTransaction extends Persistent
|
||||
throw new IllegalStateException("UserSessionModel must be instance of UserSessionAdapter");
|
||||
}
|
||||
|
||||
AuthenticatedClientSessionStore clientSessions = sessionToImportInto.getEntity().getAuthenticatedClientSessions();
|
||||
UUID existingId = clientSessions.put(client.getId(), clientSessionId);
|
||||
|
||||
if (!Objects.equals(existingId, clientSessionId)) {
|
||||
userSessionTx.registerClientSession(sessionToImportInto.getId(), client.getClientId(), clientSessionId, offline);
|
||||
if (sessionToImportInto.getEntity().getClientSessions().add(client.getId())) {
|
||||
userSessionTx.registerClientSession(sessionToImportInto.getId(), client.getId(), offline);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
|
||||
@ -32,7 +32,6 @@ import org.keycloak.models.session.PersistentAuthenticatedClientSessionAdapter;
|
||||
import org.keycloak.models.session.PersistentUserSessionAdapter;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.utils.RealmModelDelegate;
|
||||
@ -46,7 +45,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
@ -200,7 +198,7 @@ public class JpaChangesPerformer<K, V extends SessionEntity> {
|
||||
};
|
||||
PersistentAuthenticatedClientSessionAdapter clientSessionModel = (PersistentAuthenticatedClientSessionAdapter) userSessionPersister.loadClientSession(realm, client, userSession, entity.isOffline());
|
||||
if (clientSessionModel != null) {
|
||||
AuthenticatedClientSessionEntity authenticatedClientSessionEntity = new AuthenticatedClientSessionEntity(entity.getId()) {
|
||||
AuthenticatedClientSessionEntity authenticatedClientSessionEntity = new AuthenticatedClientSessionEntity() {
|
||||
@Override
|
||||
public Map<String, String> getNotes() {
|
||||
return new HashMap<>() {
|
||||
@ -289,11 +287,6 @@ public class JpaChangesPerformer<K, V extends SessionEntity> {
|
||||
notes.forEach(clientSessionModel::setNote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return UUID.fromString(clientSessionModel.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) {
|
||||
throw new IllegalStateException("not implemented");
|
||||
@ -341,7 +334,7 @@ public class JpaChangesPerformer<K, V extends SessionEntity> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId().toString();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -647,16 +640,6 @@ public class JpaChangesPerformer<K, V extends SessionEntity> {
|
||||
userSessionModel.setState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedClientSessionStore getAuthenticatedClientSessions() {
|
||||
return new AuthenticatedClientSessionStore() {
|
||||
@Override
|
||||
public void clear() {
|
||||
userSessionModel.getAuthenticatedClientSessions().clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealmId() {
|
||||
return userSessionModel.getRealm().getId();
|
||||
@ -738,11 +721,6 @@ public class JpaChangesPerformer<K, V extends SessionEntity> {
|
||||
notes.forEach(userSessionModel::setNote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatedClientSessions(AuthenticatedClientSessionStore authenticatedClientSessions) {
|
||||
throw new IllegalStateException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel.State getState() {
|
||||
return userSessionModel.getState();
|
||||
|
||||
@ -28,7 +28,6 @@ import org.keycloak.models.sessions.infinispan.PersistentUserSessionProvider;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||
@ -129,7 +128,7 @@ public class UserSessionPersistentChangelogBasedTransaction extends PersistentSe
|
||||
return isScheduledForRemove(getUpdates(offline).get(key));
|
||||
}
|
||||
|
||||
public void registerClientSession(String userSessionId, String clientId, UUID clientSessionId, boolean offline) {
|
||||
public void registerClientSession(String userSessionId, String clientId, boolean offline) {
|
||||
addTask(userSessionId, new PersistentSessionUpdateTask<>() {
|
||||
@Override
|
||||
public boolean isOffline() {
|
||||
@ -138,7 +137,7 @@ public class UserSessionPersistentChangelogBasedTransaction extends PersistentSe
|
||||
|
||||
@Override
|
||||
public void runUpdate(UserSessionEntity entity) {
|
||||
entity.getAuthenticatedClientSessions().put(clientId, clientSessionId);
|
||||
entity.getClientSessions().add(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -19,11 +19,11 @@ package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoFactory;
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.infinispan.protostream.annotations.ProtoReserved;
|
||||
import org.infinispan.protostream.annotations.ProtoTypeId;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
@ -32,19 +32,24 @@ import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@ProtoTypeId(Marshalling.AUTHENTICATED_CLIENT_SESSION_ENTITY)
|
||||
@ProtoReserved(
|
||||
value = {7},
|
||||
names = {"id"}
|
||||
)
|
||||
public class AuthenticatedClientSessionEntity extends SessionEntity {
|
||||
|
||||
public static final Logger logger = Logger.getLogger(AuthenticatedClientSessionEntity.class);
|
||||
|
||||
// Metadata attribute, which contains the last timestamp available on remoteCache. Used in decide whether we need to write to remoteCache (DC) or not
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
public static final String LAST_TIMESTAMP_REMOTE = "lstr";
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
public static final String CLIENT_ID_NOTE = "clientId";
|
||||
|
||||
private String authMethod;
|
||||
@ -54,12 +59,11 @@ public class AuthenticatedClientSessionEntity extends SessionEntity {
|
||||
|
||||
private Map<String, String> notes = new ConcurrentHashMap<>();
|
||||
|
||||
private final UUID id;
|
||||
|
||||
// TODO [pruivo] [KC27] make these fields final. They are the client session identity.
|
||||
private volatile String userSessionId;
|
||||
private volatile String clientId;
|
||||
|
||||
public AuthenticatedClientSessionEntity(UUID id) {
|
||||
this.id = id;
|
||||
public AuthenticatedClientSessionEntity() {
|
||||
}
|
||||
|
||||
@ProtoField(2)
|
||||
@ -103,12 +107,14 @@ public class AuthenticatedClientSessionEntity extends SessionEntity {
|
||||
return Boolean.parseBoolean(getNotes().get(AuthenticatedClientSessionModel.USER_SESSION_REMEMBER_ME_NOTE));
|
||||
}
|
||||
|
||||
@ProtoField(9)
|
||||
public String getClientId() {
|
||||
return getNotes().get(CLIENT_ID_NOTE);
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
getNotes().put(CLIENT_ID_NOTE, clientId);
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@ProtoField(value = 5)
|
||||
@ -129,70 +135,35 @@ public class AuthenticatedClientSessionEntity extends SessionEntity {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
@ProtoField(7)
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AuthenticatedClientSessionEntity [" + "id=" + id + ']';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof AuthenticatedClientSessionEntity that)) {
|
||||
return false;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
return Objects.equals(id, that.id);
|
||||
AuthenticatedClientSessionEntity that = (AuthenticatedClientSessionEntity) o;
|
||||
return Objects.equals(userSessionId, that.userSessionId) && Objects.equals(clientId, that.clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hashCode(userSessionId);
|
||||
result = 31 * result + Objects.hashCode(clientId);
|
||||
return result;
|
||||
}
|
||||
|
||||
// factory method required because of final fields
|
||||
@ProtoFactory
|
||||
AuthenticatedClientSessionEntity(String realmId, String authMethod, String redirectUri, int timestamp, String action, Map<String, String> notes, UUID id) {
|
||||
AuthenticatedClientSessionEntity(String realmId, String authMethod, String redirectUri, int timestamp, String action, Map<String, String> notes, String userSessionId, String clientId) {
|
||||
super(realmId);
|
||||
this.authMethod = authMethod;
|
||||
this.redirectUri = redirectUri;
|
||||
this.timestamp = timestamp;
|
||||
this.action = action;
|
||||
this.notes = notes;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id != null ? id.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) {
|
||||
int timestampRemote = getTimestamp();
|
||||
|
||||
SessionEntityWrapper entityWrapper;
|
||||
if (localEntityWrapper == null) {
|
||||
entityWrapper = new SessionEntityWrapper<>(this);
|
||||
} else {
|
||||
AuthenticatedClientSessionEntity localClientSession = (AuthenticatedClientSessionEntity) localEntityWrapper.getEntity();
|
||||
|
||||
// local timestamp should always contain the bigger
|
||||
if (timestampRemote < localClientSession.getTimestamp()) {
|
||||
setTimestamp(localClientSession.getTimestamp());
|
||||
}
|
||||
|
||||
entityWrapper = new SessionEntityWrapper<>(localEntityWrapper.getLocalMetadata(), this);
|
||||
}
|
||||
|
||||
entityWrapper.putLocalMetadataNoteInt(LAST_TIMESTAMP_REMOTE, timestampRemote);
|
||||
|
||||
logger.debugf("Updating client session entity %s. timestamp=%d, timestampRemote=%d", getId(), getTimestamp(), timestampRemote);
|
||||
|
||||
return entityWrapper;
|
||||
this.userSessionId = userSessionId;
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@ProtoField(8)
|
||||
public String getUserSessionId() {
|
||||
return userSessionId;
|
||||
}
|
||||
@ -201,8 +172,8 @@ public class AuthenticatedClientSessionEntity extends SessionEntity {
|
||||
this.userSessionId = userSessionId;
|
||||
}
|
||||
|
||||
public static AuthenticatedClientSessionEntity create(UUID clientSessionId, RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
||||
var entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
||||
public static AuthenticatedClientSessionEntity create(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
||||
var entity = new AuthenticatedClientSessionEntity();
|
||||
entity.setRealmId(realm.getId());
|
||||
entity.setClientId(client.getId());
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
@ -215,7 +186,7 @@ public class AuthenticatedClientSessionEntity extends SessionEntity {
|
||||
}
|
||||
|
||||
public static AuthenticatedClientSessionEntity createFromModel(AuthenticatedClientSessionModel model) {
|
||||
var entity = create(UUID.fromString(model.getId()), model.getRealm(), model.getClient(), model.getUserSession());
|
||||
var entity = create(model.getRealm(), model.getClient(), model.getUserSession());
|
||||
entity.setNotes(model.getNotes() == null ? new ConcurrentHashMap<>() : model.getNotes());
|
||||
return entity;
|
||||
}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
import org.infinispan.protostream.annotations.Proto;
|
||||
import org.infinispan.protostream.annotations.ProtoTypeId;
|
||||
import org.keycloak.marshalling.Marshalling;
|
||||
|
||||
/**
|
||||
* The key stored in the {@link org.infinispan.Cache} for {@link AuthenticatedClientSessionEntity}.
|
||||
* <p>
|
||||
* Although this class is the same as {@link ClientSessionKey}, we keep them separates so they can evolve independent.
|
||||
*/
|
||||
@ProtoTypeId(Marshalling.EMBEDDED_CLIENT_SESSION_KEY)
|
||||
@Proto
|
||||
public record EmbeddedClientSessionKey(String userSessionId, String clientId) {
|
||||
|
||||
public String toId() {
|
||||
return userSessionId + "::" + clientId;
|
||||
}
|
||||
|
||||
}
|
||||
@ -55,6 +55,8 @@ public abstract class SessionEntity {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
//no longer used
|
||||
public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) {
|
||||
if (localEntityWrapper == null) {
|
||||
return new SessionEntityWrapper<>(this);
|
||||
|
||||
@ -17,13 +17,15 @@
|
||||
|
||||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoFactory;
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.infinispan.protostream.annotations.ProtoReserved;
|
||||
import org.infinispan.protostream.annotations.ProtoTypeId;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
@ -38,6 +40,10 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@ProtoTypeId(Marshalling.USER_SESSION_ENTITY)
|
||||
@ProtoReserved(
|
||||
value = {11},
|
||||
names = {"authenticatedClientSessions"}
|
||||
)
|
||||
public class UserSessionEntity extends SessionEntity {
|
||||
|
||||
public static final Logger logger = Logger.getLogger(UserSessionEntity.class);
|
||||
@ -66,12 +72,16 @@ public class UserSessionEntity extends SessionEntity {
|
||||
|
||||
private UserSessionModel.State state;
|
||||
|
||||
private final Set<String> clientSessions = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private Map<String, String> notes = new ConcurrentHashMap<>();
|
||||
|
||||
public UserSessionEntity(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@ProtoFactory
|
||||
static UserSessionEntity protoFactory(String realmId, String id, String user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, int started, int lastSessionRefresh, Map<String, String> notes, AuthenticatedClientSessionStore authenticatedClientSessions, UserSessionModel.State state, String brokerSessionId, String brokerUserId) {
|
||||
static UserSessionEntity protoFactory(String realmId, String id, String user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, int started, int lastSessionRefresh, Map<String, String> notes, UserSessionModel.State state, String brokerSessionId, String brokerUserId, Set<String> clientSessions) {
|
||||
var entity = new UserSessionEntity(id);
|
||||
entity.setRealmId(realmId);
|
||||
entity.setUser(user);
|
||||
@ -85,7 +95,7 @@ public class UserSessionEntity extends SessionEntity {
|
||||
entity.setBrokerUserId(brokerUserId);
|
||||
entity.setState(state);
|
||||
entity.setNotes(notes);
|
||||
entity.setAuthenticatedClientSessions(authenticatedClientSessions);
|
||||
entity.getClientSessions().addAll(clientSessions);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@ -94,10 +104,6 @@ public class UserSessionEntity extends SessionEntity {
|
||||
return id;
|
||||
}
|
||||
|
||||
private Map<String, String> notes = new ConcurrentHashMap<>();
|
||||
|
||||
private AuthenticatedClientSessionStore authenticatedClientSessions = new AuthenticatedClientSessionStore();
|
||||
|
||||
@ProtoField(3)
|
||||
public String getUser() {
|
||||
return user;
|
||||
@ -170,15 +176,6 @@ public class UserSessionEntity extends SessionEntity {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
@ProtoField(11)
|
||||
public AuthenticatedClientSessionStore getAuthenticatedClientSessions() {
|
||||
return authenticatedClientSessions;
|
||||
}
|
||||
|
||||
public void setAuthenticatedClientSessions(AuthenticatedClientSessionStore authenticatedClientSessions) {
|
||||
this.authenticatedClientSessions = authenticatedClientSessions;
|
||||
}
|
||||
|
||||
@ProtoField(value = 12)
|
||||
public UserSessionModel.State getState() {
|
||||
return state;
|
||||
@ -206,6 +203,11 @@ public class UserSessionEntity extends SessionEntity {
|
||||
this.brokerUserId = brokerUserId;
|
||||
}
|
||||
|
||||
@ProtoField(value = 15, collectionImplementation = HashSet.class)
|
||||
public Set<String> getClientSessions() {
|
||||
return clientSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@ -224,7 +226,7 @@ public class UserSessionEntity extends SessionEntity {
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("UserSessionEntity [id=%s, realm=%s, lastSessionRefresh=%d, clients=%s]", getId(), getRealmId(), getLastSessionRefresh(),
|
||||
new TreeSet(this.authenticatedClientSessions.keySet()));
|
||||
clientSessions);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -283,7 +285,6 @@ public class UserSessionEntity extends SessionEntity {
|
||||
entity.setBrokerUserId(userSession.getBrokerUserId());
|
||||
entity.setIpAddress(userSession.getIpAddress());
|
||||
entity.setNotes(userSession.getNotes() == null ? new ConcurrentHashMap<>() : userSession.getNotes());
|
||||
entity.setAuthenticatedClientSessions(new AuthenticatedClientSessionStore());
|
||||
entity.setRememberMe(userSession.isRememberMe());
|
||||
entity.setState(userSession.getState());
|
||||
if (userSession instanceof OfflineUserSessionModel offline) {
|
||||
|
||||
@ -95,10 +95,7 @@ public class RemoteUserSessionProvider implements UserSessionProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, String clientSessionId, boolean offline) {
|
||||
if (clientSessionId == null) {
|
||||
return null;
|
||||
}
|
||||
public AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, boolean offline) {
|
||||
var clientTx = getClientSessionTransaction(offline);
|
||||
var updater = clientTx.get(new ClientSessionKey(userSession.getId(), client.getId()));
|
||||
if (updater == null) {
|
||||
@ -317,7 +314,7 @@ public class RemoteUserSessionProvider implements UserSessionProvider {
|
||||
userSessionBuffer.add(userSessionModel.getId());
|
||||
for (var clientSessionModel : userSessionModel.getAuthenticatedClientSessions().values()) {
|
||||
var clientSessionKey = new ClientSessionKey(userSessionModel.getId(), clientSessionModel.getClient().getId());
|
||||
clientSessionBuffer.add(Map.entry(userSessionModel.getId(), clientSessionModel.getId()));
|
||||
clientSessionBuffer.add(Map.entry(clientSessionKey.userSessionId(), clientSessionKey.clientId()));
|
||||
var clientSessionEntity = RemoteAuthenticatedClientSessionEntity.createFromModel(clientSessionKey, clientSessionModel);
|
||||
stage.dependsOn(clientSessionCache.putIfAbsentAsync(clientSessionKey, clientSessionEntity));
|
||||
}
|
||||
|
||||
@ -49,6 +49,6 @@ public class AuthClientSessionSetMapper implements Function<Map.Entry<String, Se
|
||||
|
||||
@Override
|
||||
public Set<String> apply(Map.Entry<String, SessionEntityWrapper<UserSessionEntity>> entry) {
|
||||
return entry.getValue().getEntity().getAuthenticatedClientSessions().keySet();
|
||||
return entry.getValue().getEntity().getClientSessions();
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
|
||||
|
||||
return realm.equals(entity.getRealmId()) &&
|
||||
(user == null || entity.getUser().equals(user)) &&
|
||||
(client == null || (entity.getAuthenticatedClientSessions() != null && entity.getAuthenticatedClientSessions().containsKey(client))) &&
|
||||
(client == null || entity.getClientSessions().contains(client)) &&
|
||||
(brokerSessionId == null || brokerSessionId.equals(entity.getBrokerSessionId())) &&
|
||||
(brokerUserId == null || brokerUserId.equals(entity.getBrokerUserId()));
|
||||
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.keycloak.migration.migrators;
|
||||
|
||||
import org.keycloak.migration.ModelVersion;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
|
||||
public class MigrateTo26_4_0 extends RealmMigration {
|
||||
|
||||
public static final ModelVersion VERSION = new ModelVersion("26.4.0");
|
||||
|
||||
@Override
|
||||
public ModelVersion getVersion() {
|
||||
return VERSION;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void migrate(KeycloakSession session) {
|
||||
UserSessionProvider provider = session.getProvider(UserSessionProvider.class);
|
||||
if (provider != null) {
|
||||
provider.migrate(VERSION.toString());
|
||||
}
|
||||
super.migrate(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrateRealm(KeycloakSession session, RealmModel realm) {
|
||||
}
|
||||
|
||||
}
|
||||
@ -31,6 +31,7 @@ import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -245,15 +246,14 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof AuthenticatedClientSessionModel)) return false;
|
||||
if (!(o instanceof AuthenticatedClientSessionModel that)) return false;
|
||||
|
||||
AuthenticatedClientSessionModel that = (AuthenticatedClientSessionModel) o;
|
||||
return that.getId().equals(getId());
|
||||
return Objects.equals(getId(), that.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
return Objects.hashCode(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -44,6 +44,7 @@ import org.keycloak.migration.migrators.MigrateTo26_0_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo26_1_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo26_2_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo26_3_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo26_4_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo2_0_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo2_1_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo2_2_0;
|
||||
@ -127,6 +128,7 @@ public class DefaultMigrationManager implements MigrationManager {
|
||||
new MigrateTo26_1_0(),
|
||||
new MigrateTo26_2_0(),
|
||||
new MigrateTo26_3_0(),
|
||||
new MigrateTo26_4_0(),
|
||||
};
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
||||
@ -94,7 +94,7 @@ public interface AuthenticatedClientSessionModel extends CommonClientSessionMode
|
||||
}
|
||||
|
||||
/**
|
||||
* deprecated use {@link #setRefreshTokenUseCount(String, int)}
|
||||
* @deprecated use {@link #setRefreshTokenUseCount(String, int)}
|
||||
*/
|
||||
@Deprecated
|
||||
default void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) {
|
||||
@ -137,6 +137,6 @@ public interface AuthenticatedClientSessionModel extends CommonClientSessionMode
|
||||
removeNote(note);
|
||||
}
|
||||
}
|
||||
getNotes().put(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(getTimestamp()));
|
||||
setNote(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(getTimestamp()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,22 +34,44 @@ public interface UserSessionProvider extends Provider {
|
||||
/**
|
||||
* Returns currently used Keycloak session.
|
||||
* @return {@link KeycloakSession}
|
||||
* @deprecated for removal.
|
||||
*/
|
||||
// It is not used anywhere. Remove it?
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
KeycloakSession getKeycloakSession();
|
||||
|
||||
AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getClientSession(UserSessionModel, ClientModel, String, boolean)} instead.
|
||||
* @deprecated Use {@link #getClientSession(UserSessionModel, ClientModel, boolean)} instead.
|
||||
*/
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
default AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline) {
|
||||
return getClientSession(userSession, client, clientSessionId == null ? null : clientSessionId.toString(), offline);
|
||||
return getClientSession(userSession, client, offline);
|
||||
}
|
||||
AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, String clientSessionId, boolean offline);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getClientSession(UserSessionModel, ClientModel, boolean)} instead.
|
||||
*/
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
default AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, String clientSessionId, boolean offline) {
|
||||
return getClientSession(userSession, client, offline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the authenticated client session for a given user session and client.
|
||||
*
|
||||
* @param userSession The user's session model.
|
||||
* @param client The client model.
|
||||
* @param offline If {@code true}, retrieves the offline session; otherwise, retrieves the online session.
|
||||
* @return The authenticated client session, or {@code null} if it doesn't exist.
|
||||
*/
|
||||
AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, boolean offline);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #createUserSession(String, RealmModel, UserModel, String, String, String, boolean, String, String, UserSessionModel.SessionPersistenceState)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
default UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
|
||||
return createUserSession(null, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId,
|
||||
brokerUserId, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||
@ -221,7 +243,7 @@ public interface UserSessionProvider extends Provider {
|
||||
*
|
||||
* @deprecated Deprecated as offline session preloading was removed in KC25. This method will be removed in KC27.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
default void importUserSessions(Collection<UserSessionModel> persistentUserSessions, boolean offline) {}
|
||||
|
||||
void close();
|
||||
|
||||
@ -25,7 +25,6 @@ import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.TokenVerifier.Predicate;
|
||||
import org.keycloak.TokenVerifier.TokenTypeCheck;
|
||||
import org.keycloak.authentication.AuthenticationFlowException;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
@ -109,7 +108,6 @@ import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -271,7 +269,7 @@ public class AuthenticationManager {
|
||||
ClientConnection connection, HttpHeaders headers,
|
||||
boolean logoutBroker) {
|
||||
|
||||
return backchannelLogout(session, realm, userSession, uriInfo, connection, headers, logoutBroker, userSession == null ? false : userSession.isOffline());
|
||||
return backchannelLogout(session, realm, userSession, uriInfo, connection, headers, logoutBroker, userSession != null && userSession.isOffline());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1161,7 +1159,7 @@ public class AuthenticationManager {
|
||||
getClientScopesToApproveOnConsentScreen(grantedConsent, session, authSession);
|
||||
|
||||
// Skip grant screen if everything was already approved by this user
|
||||
if (clientScopesToApprove.size() > 0) {
|
||||
if (!clientScopesToApprove.isEmpty()) {
|
||||
String execution = AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name();
|
||||
|
||||
ClientSessionCode<AuthenticationSessionModel> accessCode =
|
||||
@ -1727,7 +1725,7 @@ public class AuthenticationManager {
|
||||
Map<String, AuthenticatedClientSessionModel> clientSessions = userSession.getAuthenticatedClientSessions();
|
||||
|
||||
return clientSessions.values().stream().filter(c -> c.getClient().equals(client))
|
||||
.map((c) -> c.getNotes().get(OIDCLoginProtocol.SCOPE_PARAM))
|
||||
.map((c) -> c.getNote(OIDCLoginProtocol.SCOPE_PARAM))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
@ -25,10 +25,8 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
@ -58,6 +56,7 @@ import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.PersistentUserSessionProvider;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.EmbeddedClientSessionKey;
|
||||
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
@ -159,7 +158,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
RealmModel realm = session.realms().getRealm(realmId);
|
||||
session.getContext().setRealm(realm);
|
||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||
session.sessions().getUserSessionsStream(realm, testApp).collect(Collectors.toList())
|
||||
session.sessions().getUserSessionsStream(realm, testApp).toList()
|
||||
.forEach(userSessionLooper -> persistUserSession(session, userSessionLooper, true));
|
||||
});
|
||||
|
||||
@ -195,10 +194,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
int started = Time.currentTime();
|
||||
|
||||
AtomicReference<UserSessionModel[]> origSessionsAt = new AtomicReference<>();
|
||||
AtomicReference<List<UserSessionModel>> loadedSessionsAt = new AtomicReference<>();
|
||||
|
||||
AtomicReference<UserSessionModel> userSessionAt = new AtomicReference<>();
|
||||
AtomicReference<UserSessionModel> persistedSessionAt = new AtomicReference<>();
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
// Create some sessions in infinispan
|
||||
@ -225,10 +221,8 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
|
||||
// Load offline session
|
||||
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(session, true, 10, 1, 1);
|
||||
loadedSessionsAt.set(loadedSessions);
|
||||
|
||||
UserSessionModel persistedSession = loadedSessions.get(0);
|
||||
persistedSessionAt.set(persistedSession);
|
||||
|
||||
assertSession(persistedSession, session.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, started, "test-app");
|
||||
|
||||
@ -391,7 +385,6 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
final String username = "my-user";
|
||||
final String clientId = "my-app";
|
||||
final AtomicReference<String> userSessionID = new AtomicReference<>();
|
||||
final AtomicReference<String> clientSessionId = new AtomicReference<>();
|
||||
|
||||
// create user and client
|
||||
inComittedTransaction(session -> {
|
||||
@ -405,8 +398,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
UserSessionModel userSession = session.sessions().createUserSession(null, realm, session.users().getUserByUsername(realm, username), username, "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||
userSessionID.set(userSession.getId());
|
||||
|
||||
AuthenticatedClientSessionModel clientSession = createClientSession(session, realm.getId(), realm.getClientByClientId(clientId), userSession, "http://redirect", "state");
|
||||
clientSessionId.set(clientSession.getId());
|
||||
createClientSession(session, realm.getId(), realm.getClientByClientId(clientId), userSession, "http://redirect", "state");
|
||||
});
|
||||
|
||||
if (InfinispanUtils.isEmbeddedInfinispan()) {
|
||||
@ -415,8 +407,9 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
||||
session.getContext().setRealm(realm);
|
||||
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessoinCache = session.getProvider(InfinispanConnectionProvider.class).getCache(CLIENT_SESSION_CACHE_NAME);
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> clientSession = clientSessoinCache.get(UUID.fromString(clientSessionId.get()));
|
||||
var cacheKey = new EmbeddedClientSessionKey(userSessionID.get(), realm.getClientByClientId(clientId).getId());
|
||||
Cache<EmbeddedClientSessionKey, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessoinCache = session.getProvider(InfinispanConnectionProvider.class).getCache(CLIENT_SESSION_CACHE_NAME);
|
||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> clientSession = clientSessoinCache.get(cacheKey);
|
||||
assertNotNull(clientSession);
|
||||
assertNotNull(clientSession.getEntity());
|
||||
// user session id is not stored in the cache
|
||||
@ -440,7 +433,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
.getTimestamp();
|
||||
}
|
||||
return session.sessions()
|
||||
.getClientSession(userSession, client, clientSessionId.get(), false)
|
||||
.getClientSession(userSession, client, false)
|
||||
.getTimestamp();
|
||||
};
|
||||
|
||||
@ -455,7 +448,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionID.get());
|
||||
session.sessions()
|
||||
.getClientSession(userSession, client, clientSessionId.get(), false)
|
||||
.getClientSession(userSession, client, false)
|
||||
.setTimestamp(currentTimestamp + 10);
|
||||
});
|
||||
|
||||
@ -723,6 +716,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated(since = "26.4", forRemoval = true)
|
||||
public void testMigrateSession() {
|
||||
Assume.assumeTrue(MultiSiteUtils.isPersistentSessionsEnabled());
|
||||
Assume.assumeTrue(InfinispanUtils.isEmbeddedInfinispan());
|
||||
@ -745,7 +739,6 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
// trigger a migration with the entries that are still in the cache
|
||||
PersistentUserSessionProvider userSessionProvider = (PersistentUserSessionProvider) session.getProvider(UserSessionProvider.class);
|
||||
userSessionProvider.migrateNonPersistentSessionsToPersistentSessions();
|
||||
JpaUserSessionPersisterProvider sessionPersisterProvider = (JpaUserSessionPersisterProvider) session.getProvider(UserSessionPersisterProvider.class);
|
||||
|
||||
// verify that import was complete
|
||||
Assert.assertEquals(sessions.length, countUserSessionsInRealm(session));
|
||||
@ -830,8 +823,6 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
private List<UserSessionModel> loadPersistedSessionsPaginated(KeycloakSession session, boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
|
||||
int count = persister.getUserSessionsCount(offline);
|
||||
|
||||
int pageCount = 0;
|
||||
boolean next = true;
|
||||
List<UserSessionModel> result = new ArrayList<>();
|
||||
@ -840,13 +831,13 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||
while (next) {
|
||||
List<UserSessionModel> sess = persister
|
||||
.loadUserSessionsStream(0, sessionsPerPage, offline, lastSessionId)
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
|
||||
if (sess.size() < sessionsPerPage) {
|
||||
next = false;
|
||||
|
||||
// We had at least some session
|
||||
if (sess.size() > 0) {
|
||||
if (!sess.isEmpty()) {
|
||||
pageCount++;
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
package org.keycloak.testsuite.model.session;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.BrokenBarrierException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -25,8 +24,6 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
@ -151,9 +148,6 @@ public class UserSessionProviderModelTest extends KeycloakModelTest {
|
||||
return createSessions(session, realmId);
|
||||
});
|
||||
|
||||
AtomicReference<List<String>> clientSessionIds = new AtomicReference<>();
|
||||
clientSessionIds.set(origSessions[0].getAuthenticatedClientSessions().values().stream().map(AuthenticatedClientSessionModel::getId).collect(Collectors.toList()));
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
RealmModel realm = session.realms().getRealm(realmId);
|
||||
session.getContext().setRealm(realm);
|
||||
@ -162,7 +156,6 @@ public class UserSessionProviderModelTest extends KeycloakModelTest {
|
||||
Assert.assertEquals(origSessions[0], userSession);
|
||||
|
||||
AuthenticatedClientSessionModel clientSession = session.sessions().getClientSession(userSession, realm.getClientByClientId("test-app"),
|
||||
origSessions[0].getAuthenticatedClientSessionByClient(realm.getClientByClientId("test-app").getId()).getId(),
|
||||
false);
|
||||
Assert.assertEquals(origSessions[0].getAuthenticatedClientSessionByClient(realm.getClientByClientId("test-app").getId()).getId(), clientSession.getId());
|
||||
|
||||
@ -182,10 +175,8 @@ public class UserSessionProviderModelTest extends KeycloakModelTest {
|
||||
Assert.assertEquals(origSessions[0], userSession);
|
||||
|
||||
// assert the client sessions are expired
|
||||
clientSessionIds.get().forEach(clientSessionId -> {
|
||||
Assert.assertNull(session.sessions().getClientSession(userSession, realm.getClientByClientId("test-app"), clientSessionId, false));
|
||||
Assert.assertNull(session.sessions().getClientSession(userSession, realm.getClientByClientId("third-party"), clientSessionId, false));
|
||||
});
|
||||
Assert.assertNull(session.sessions().getClientSession(userSession, realm.getClientByClientId("test-app"), false));
|
||||
Assert.assertNull(session.sessions().getClientSession(userSession, realm.getClientByClientId("third-party"), false));
|
||||
});
|
||||
} finally {
|
||||
setTimeOffset(0);
|
||||
@ -207,10 +198,10 @@ public class UserSessionProviderModelTest extends KeycloakModelTest {
|
||||
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
|
||||
|
||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp, userSession);
|
||||
session.sessions().createClientSession(realm, testApp, userSession);
|
||||
|
||||
// assert the client sessions are present
|
||||
assertThat(session.sessions().getClientSession(userSession, testApp, clientSession.getId(), false), notNullValue());
|
||||
assertThat(session.sessions().getClientSession(userSession, testApp, false), notNullValue());
|
||||
return userSession.getId();
|
||||
});
|
||||
|
||||
@ -225,27 +216,22 @@ public class UserSessionProviderModelTest extends KeycloakModelTest {
|
||||
|
||||
@Test
|
||||
public void testClientSessionIsNotPersistedForTransientUserSession() {
|
||||
Object[] transientUserSessionWithClientSessionId = inComittedTransaction(session -> {
|
||||
UserSessionModel userSession = inComittedTransaction(session -> {
|
||||
RealmModel realm = session.realms().getRealm(realmId);
|
||||
session.getContext().setRealm(realm);
|
||||
UserSessionModel userSession = session.sessions().createUserSession(null, realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
|
||||
UserSessionModel us = session.sessions().createUserSession(null, realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
|
||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp, userSession);
|
||||
session.sessions().createClientSession(realm, testApp, us);
|
||||
|
||||
// assert the client sessions are present
|
||||
assertThat(session.sessions().getClientSession(userSession, testApp, clientSession.getId(), false), notNullValue());
|
||||
Object[] result = new Object[2];
|
||||
result[0] = userSession;
|
||||
result[1] = clientSession.getId();
|
||||
return result;
|
||||
assertThat(session.sessions().getClientSession(us, testApp, false), notNullValue());
|
||||
return us;
|
||||
});
|
||||
inComittedTransaction(session -> {
|
||||
RealmModel realm = session.realms().getRealm(realmId);
|
||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||
UserSessionModel userSession = (UserSessionModel) transientUserSessionWithClientSessionId[0];
|
||||
String clientSessionId = (String) transientUserSessionWithClientSessionId[1];
|
||||
// in new transaction transient session should not be present
|
||||
assertThat(session.sessions().getClientSession(userSession, testApp, clientSessionId, false), nullValue());
|
||||
assertThat(session.sessions().getClientSession(userSession, testApp, false), nullValue());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ public abstract class AbstractSessionCacheCommand extends AbstractCommand {
|
||||
}
|
||||
|
||||
protected String toString(UserSessionEntity userSession) {
|
||||
int clientSessionsSize = userSession.getAuthenticatedClientSessions()==null ? 0 : userSession.getAuthenticatedClientSessions().size();
|
||||
int clientSessionsSize = userSession.getClientSessions().size();
|
||||
return "ID: " + userSession.getId() + ", realm: " + userSession.getRealmId()+ ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
|
||||
", authenticatedClientSessions: " + clientSessionsSize;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user