Reuse already fixed code to fetch offline user (#22429)
The problem is again the wrap(...) function. In case the user is not found, then null is returned. This can happen when a federated user is deleted on the federation side but Keycloak is not informed about it. In that case, the session is still present but no UserModel can be created. Without this patch the stream contains null values. Some downstream users can not cope well with that. The adjustment of the function getUserSessionsCount(...) is slightly more expensive in execution, but returns the correct number. Closes #22428 Co-authored-by: Martin Krüger <mkrueger@mkru.de>
This commit is contained in:
parent
47d9ae71c4
commit
498be3d928
1 changed files with 26 additions and 39 deletions
|
@ -65,6 +65,7 @@ import org.keycloak.models.sessions.infinispan.util.SessionTimeouts;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -331,9 +332,13 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
private UserSessionEntity getUserSessionEntity(RealmModel realm, String id, boolean offline) {
|
private UserSessionEntity getUserSessionEntity(RealmModel realm, String id, boolean offline) {
|
||||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
|
||||||
SessionEntityWrapper<UserSessionEntity> entityWrapper = tx.get(id);
|
SessionEntityWrapper<UserSessionEntity> entityWrapper = tx.get(id);
|
||||||
if (entityWrapper==null) return null;
|
if (entityWrapper == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
UserSessionEntity entity = entityWrapper.getEntity();
|
UserSessionEntity entity = entityWrapper.getEntity();
|
||||||
if (!entity.getRealmId().equals(realm.getId())) return null;
|
if (!entity.getRealmId().equals(realm.getId())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,23 +477,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
return persister.loadUserSessionsStream(realm, client, true, firstResult, maxResults);
|
return persister.loadUserSessionsStream(realm, client, true, firstResult, maxResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String clientUuid = client.getId();
|
UserSessionPredicate predicate = UserSessionPredicate.create(realm.getId()).client(client.getId());
|
||||||
UserSessionPredicate predicate = UserSessionPredicate.create(realm.getId()).client(clientUuid);
|
|
||||||
|
|
||||||
return getUserSessionModels(realm, firstResult, maxResults, offline, predicate);
|
return paginatedStream(getUserSessionsStream(realm, predicate, offline)
|
||||||
}
|
.sorted(Comparator.comparing(UserSessionModel::getLastSessionRefresh)), firstResult, maxResults);
|
||||||
|
|
||||||
protected Stream<UserSessionModel> getUserSessionModels(RealmModel realm, Integer firstResult, Integer maxResults, boolean offline, UserSessionPredicate predicate) {
|
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
|
|
||||||
cache = CacheDecorators.skipCacheLoaders(cache);
|
|
||||||
|
|
||||||
// return a stream that 'wraps' the infinispan cache stream so that the cache stream's elements are read one by one
|
|
||||||
// and then filtered/mapped locally to avoid serialization issues when trying to manipulate the cache stream directly.
|
|
||||||
Stream<UserSessionEntity> stream = StreamSupport.stream(cache.entrySet().stream().filter(predicate).spliterator(), false)
|
|
||||||
.map(Mappers.userSessionEntity())
|
|
||||||
.sorted(Comparators.userSessionLastSessionRefresh());
|
|
||||||
|
|
||||||
return paginatedStream(stream, firstResult, maxResults).map(entity -> this.wrap(realm, entity, offline));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -571,7 +563,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
return cache.entrySet().stream()
|
return cache.entrySet().stream()
|
||||||
.filter(UserSessionPredicate.create(realm.getId()))
|
.filter(UserSessionPredicate.create(realm.getId()))
|
||||||
.map(Mappers.authClientSessionSetMapper())
|
.map(Mappers.authClientSessionSetMapper())
|
||||||
.flatMap((Serializable & Function<Set<String>, Stream<? extends String>>)Mappers::toStream)
|
.flatMap((Serializable & Function<Set<String>, Stream<? extends String>>) Mappers::toStream)
|
||||||
.collect(
|
.collect(
|
||||||
CacheCollectors.serializableCollector(
|
CacheCollectors.serializableCollector(
|
||||||
() -> Collectors.groupingBy(Function.identity(), Collectors.counting())
|
() -> Collectors.groupingBy(Function.identity(), Collectors.counting())
|
||||||
|
@ -587,14 +579,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
return persister.getUserSessionsCount(realm, client, true);
|
return persister.getUserSessionsCount(realm, client, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
|
return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).client(client.getId()), offline).count();
|
||||||
cache = CacheDecorators.skipCacheLoaders(cache);
|
|
||||||
|
|
||||||
final String clientUuid = client.getId();
|
|
||||||
|
|
||||||
return cache.entrySet().stream()
|
|
||||||
.filter(UserSessionPredicate.create(realm.getId()).client(clientUuid))
|
|
||||||
.count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -770,12 +755,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
AuthenticatedClientSessionAdapter wrap(UserSessionModel userSession, ClientModel client, AuthenticatedClientSessionEntity entity, boolean offline) {
|
AuthenticatedClientSessionAdapter wrap(UserSessionModel userSession, ClientModel client, AuthenticatedClientSessionEntity entity, boolean offline) {
|
||||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
|
||||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
|
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
|
||||||
return entity != null ? new AuthenticatedClientSessionAdapter(session,this, entity, client, userSession, clientSessionUpdateTx, offline) : null;
|
return entity != null ? new AuthenticatedClientSessionAdapter(session, this, entity, client, userSession, clientSessionUpdateTx, offline) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserSessionEntity getUserSessionEntity(RealmModel realm, UserSessionModel userSession, boolean offline) {
|
UserSessionEntity getUserSessionEntity(RealmModel realm, UserSessionModel userSession, boolean offline) {
|
||||||
if (userSession instanceof UserSessionAdapter) {
|
if (userSession instanceof UserSessionAdapter) {
|
||||||
if (!userSession.getRealm().equals(realm)) return null;
|
if (!userSession.getRealm().equals(realm)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return ((UserSessionAdapter) userSession).getEntity();
|
return ((UserSessionAdapter) userSession).getEntity();
|
||||||
} else {
|
} else {
|
||||||
return getUserSessionEntity(realm, userSession.getId(), offline);
|
return getUserSessionEntity(realm, userSession.getId(), offline);
|
||||||
|
@ -996,8 +983,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
long lifespan = lifespanMsCalculator.apply(currentRealm, client, sessionEntity);
|
long lifespan = lifespanMsCalculator.apply(currentRealm, client, sessionEntity);
|
||||||
long maxIdle = maxIdleTimeMsCalculator.apply(currentRealm, client, sessionEntity);
|
long maxIdle = maxIdleTimeMsCalculator.apply(currentRealm, client, sessionEntity);
|
||||||
|
|
||||||
if(lifespan != SessionTimeouts.ENTRY_EXPIRED_FLAG
|
if (lifespan != SessionTimeouts.ENTRY_EXPIRED_FLAG
|
||||||
&& maxIdle != SessionTimeouts.ENTRY_EXPIRED_FLAG ) {
|
&& maxIdle != SessionTimeouts.ENTRY_EXPIRED_FLAG) {
|
||||||
if (cache instanceof RemoteCache) {
|
if (cache instanceof RemoteCache) {
|
||||||
Retry.executeWithBackoff((int iteration) -> {
|
Retry.executeWithBackoff((int iteration) -> {
|
||||||
|
|
||||||
|
@ -1052,7 +1039,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
if (userSession instanceof OfflineUserSessionModel) {
|
if (userSession instanceof OfflineUserSessionModel) {
|
||||||
// this is a hack so that UserModel doesn't have to be available when offline token is imported.
|
// this is a hack so that UserModel doesn't have to be available when offline token is imported.
|
||||||
// see related JIRA - KEYCLOAK-5350 and corresponding test
|
// see related JIRA - KEYCLOAK-5350 and corresponding test
|
||||||
OfflineUserSessionModel oline = (OfflineUserSessionModel)userSession;
|
OfflineUserSessionModel oline = (OfflineUserSessionModel) userSession;
|
||||||
entity.setUser(oline.getUserId());
|
entity.setUser(oline.getUserId());
|
||||||
// NOTE: Hack
|
// NOTE: Hack
|
||||||
// We skip calling entity.setLoginUsername(userSession.getLoginUsername())
|
// We skip calling entity.setLoginUsername(userSession.getLoginUsername())
|
||||||
|
@ -1098,7 +1085,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
SessionUpdateTask registerClientSessionTask = new RegisterClientSessionTask(clientSession.getClient().getId(), clientSessionId);
|
SessionUpdateTask registerClientSessionTask = new RegisterClientSessionTask(clientSession.getClient().getId(), clientSessionId);
|
||||||
userSessionUpdateTx.addTask(sessionToImportInto.getId(), registerClientSessionTask);
|
userSessionUpdateTx.addTask(sessionToImportInto.getId(), registerClientSessionTask);
|
||||||
|
|
||||||
return new AuthenticatedClientSessionAdapter(session,this, entity, clientSession.getClient(), sessionToImportInto, clientSessionUpdateTx, offline);
|
return new AuthenticatedClientSessionAdapter(session, this, entity, clientSession.getClient(), sessionToImportInto, clientSessionUpdateTx, offline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue