Leverage Infinispan lifespan for ExpirableEntities in HotRod storage
This commit is contained in:
parent
fc075a3d35
commit
5ba004b447
27 changed files with 435 additions and 62 deletions
|
@ -45,12 +45,12 @@ import org.keycloak.storage.SearchableModelField;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Spliterators;
|
import java.util.Spliterators;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import static org.keycloak.models.map.common.ExpirationUtils.isExpired;
|
|
||||||
import static org.keycloak.models.map.storage.hotRod.common.HotRodUtils.paginateQuery;
|
import static org.keycloak.models.map.storage.hotRod.common.HotRodUtils.paginateQuery;
|
||||||
import static org.keycloak.utils.StreamsUtil.closing;
|
import static org.keycloak.utils.StreamsUtil.closing;
|
||||||
|
|
||||||
|
@ -82,6 +82,17 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends Abstr
|
||||||
value = cloner.from(keyConverter.keyToString(key), value);
|
value = cloner.from(keyConverter.keyToString(key), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isExpirableEntity) {
|
||||||
|
Long lifespan = getLifespan(value);
|
||||||
|
if (lifespan != null) {
|
||||||
|
if (lifespan > 0) {
|
||||||
|
remoteCache.putIfAbsent(key, value.getHotRodEntity(), lifespan, TimeUnit.MILLISECONDS);
|
||||||
|
} else {
|
||||||
|
LOG.warnf("Skipped creation of entity %s in storage due to negative/zero lifespan.", key);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
remoteCache.putIfAbsent(key, value.getHotRodEntity());
|
remoteCache.putIfAbsent(key, value.getHotRodEntity());
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
@ -97,20 +108,28 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends Abstr
|
||||||
if (hotRodEntity == null) return null;
|
if (hotRodEntity == null) return null;
|
||||||
|
|
||||||
// Create delegate that implements Map*Entity
|
// Create delegate that implements Map*Entity
|
||||||
V delegateEntity = delegateProducer.apply(hotRodEntity);
|
return delegateProducer.apply(hotRodEntity);
|
||||||
|
|
||||||
// Check expiration if necessary and return value
|
|
||||||
return isExpirableEntity && isExpired((ExpirableEntity) delegateEntity, true) ? null : delegateEntity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public V update(V value) {
|
public V update(V value) {
|
||||||
K key = keyConverter.fromStringSafe(value.getId());
|
K key = keyConverter.fromStringSafe(value.getId());
|
||||||
|
|
||||||
|
if (isExpirableEntity) {
|
||||||
|
Long lifespan = getLifespan(value);
|
||||||
|
if (lifespan != null) {
|
||||||
|
E previousValue;
|
||||||
|
if (lifespan > 0) {
|
||||||
|
previousValue = remoteCache.replace(key, value.getHotRodEntity(), lifespan, TimeUnit.MILLISECONDS);
|
||||||
|
} else {
|
||||||
|
LOG.warnf("Removing entity %s from storage due to negative/zero lifespan.", key);
|
||||||
|
previousValue = remoteCache.remove(key);
|
||||||
|
}
|
||||||
|
return previousValue == null ? null : delegateProducer.apply(previousValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
E previousValue = remoteCache.replace(key, value.getHotRodEntity());
|
E previousValue = remoteCache.replace(key, value.getHotRodEntity());
|
||||||
if (previousValue == null) return null;
|
return previousValue == null ? null : delegateProducer.apply(previousValue);
|
||||||
|
|
||||||
return delegateProducer.apply(previousValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -127,22 +146,12 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends Abstr
|
||||||
return modelFieldName + " " + orderString;
|
return modelFieldName + " " + orderString;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String isNotExpiredIckleWhereClause() {
|
|
||||||
return "(" + IckleQueryOperators.C + ".expiration > " + Time.currentTimeMillis() + " OR "
|
|
||||||
+ IckleQueryOperators.C + ".expiration is null)";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<V> read(QueryParameters<M> queryParameters) {
|
public Stream<V> read(QueryParameters<M> queryParameters) {
|
||||||
IckleQueryMapModelCriteriaBuilder<E, M> iqmcb = queryParameters.getModelCriteriaBuilder()
|
IckleQueryMapModelCriteriaBuilder<E, M> iqmcb = queryParameters.getModelCriteriaBuilder()
|
||||||
.flashToModelCriteriaBuilder(createCriteriaBuilder());
|
.flashToModelCriteriaBuilder(createCriteriaBuilder());
|
||||||
String queryString = iqmcb.getIckleQuery();
|
String queryString = iqmcb.getIckleQuery();
|
||||||
|
|
||||||
// Temporary solution until https://github.com/keycloak/keycloak/issues/12068 is fixed
|
|
||||||
if (isExpirableEntity) {
|
|
||||||
queryString += (queryString.contains("WHERE") ? " AND " : " WHERE ") + isNotExpiredIckleWhereClause();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!queryParameters.getOrderBy().isEmpty()) {
|
if (!queryParameters.getOrderBy().isEmpty()) {
|
||||||
queryString += " ORDER BY " + queryParameters.getOrderBy().stream().map(HotRodMapStorage::toOrderString)
|
queryString += " ORDER BY " + queryParameters.getOrderBy().stream().map(HotRodMapStorage::toOrderString)
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
|
@ -232,4 +241,12 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends Abstr
|
||||||
Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, V, M>> fieldPredicates = MapFieldPredicates.getPredicates((Class<M>) storedEntityDescriptor.getModelTypeClass());
|
Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, V, M>> fieldPredicates = MapFieldPredicates.getPredicates((Class<M>) storedEntityDescriptor.getModelTypeClass());
|
||||||
return new ConcurrentHashMapKeycloakTransaction<>(this, keyConverter, cloner, fieldPredicates);
|
return new ConcurrentHashMapKeycloakTransaction<>(this, keyConverter, cloner, fieldPredicates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// V must be an instance of ExpirableEntity
|
||||||
|
// returns null if expiration field is not set
|
||||||
|
// in certain cases can return 0 or negative number, which needs to be handled carefully when using as ISPN lifespan
|
||||||
|
private Long getLifespan(V value) {
|
||||||
|
Long expiration = ((ExpirableEntity) value).getExpiration();
|
||||||
|
return expiration != null ? expiration - Time.currentTimeMillis() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,6 @@ public class HotRodRootAuthenticationSessionEntity extends AbstractHotRodEntity
|
||||||
@ProtoField(number = 4)
|
@ProtoField(number = 4)
|
||||||
public Long timestamp;
|
public Long timestamp;
|
||||||
|
|
||||||
@Basic(sortable = true)
|
|
||||||
@ProtoField(number = 5)
|
@ProtoField(number = 5)
|
||||||
public Long expiration;
|
public Long expiration;
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,14 @@ import org.infinispan.commons.marshall.ProtoStreamMarshaller;
|
||||||
import org.infinispan.protostream.GeneratedSchema;
|
import org.infinispan.protostream.GeneratedSchema;
|
||||||
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
|
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.map.storage.hotRod.locking.HotRodLocksUtils;
|
import org.keycloak.models.map.storage.hotRod.locking.HotRodLocksUtils;
|
||||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
||||||
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
|
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
|
||||||
import org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils;
|
import org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils;
|
||||||
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
@ -49,9 +51,10 @@ import static org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils.i
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
*/
|
*/
|
||||||
public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionProviderFactory {
|
public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionProviderFactory, EnvironmentDependentProviderFactory {
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "default";
|
public static final String PROVIDER_ID = "default";
|
||||||
|
public static final String SCRIPT_CACHE = "___script_cache";
|
||||||
public static final String HOT_ROD_LOCKS_CACHE_NAME = "locks";
|
public static final String HOT_ROD_LOCKS_CACHE_NAME = "locks";
|
||||||
private static final String HOT_ROD_INIT_LOCK_NAME = "HOT_ROD_INIT_LOCK";
|
private static final String HOT_ROD_INIT_LOCK_NAME = "HOT_ROD_INIT_LOCK";
|
||||||
private static final Logger LOG = Logger.getLogger(DefaultHotRodConnectionProviderFactory.class);
|
private static final Logger LOG = Logger.getLogger(DefaultHotRodConnectionProviderFactory.class);
|
||||||
|
@ -267,4 +270,9 @@ public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionP
|
||||||
.nearCacheUseBloomFilter(config.scope(cacheName).getBoolean("nearCacheBloomFilter", config.getBoolean("nearCacheBloomFilter", false)));
|
.nearCacheUseBloomFilter(config.scope(cacheName).getBoolean("nearCacheBloomFilter", config.getBoolean("nearCacheBloomFilter", false)));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,6 @@ public class HotRodAdminEventEntity extends AbstractHotRodEntity {
|
||||||
@ProtoField(number = 2)
|
@ProtoField(number = 2)
|
||||||
public String id;
|
public String id;
|
||||||
|
|
||||||
@Basic(sortable = true)
|
|
||||||
@ProtoField(number = 3)
|
@ProtoField(number = 3)
|
||||||
public Long expiration;
|
public Long expiration;
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,6 @@ public class HotRodAuthEventEntity extends AbstractHotRodEntity {
|
||||||
@ProtoField(number = 3)
|
@ProtoField(number = 3)
|
||||||
public Integer type;
|
public Integer type;
|
||||||
|
|
||||||
@Basic(sortable = true)
|
|
||||||
@ProtoField(number = 4)
|
@ProtoField(number = 4)
|
||||||
public Long expiration;
|
public Long expiration;
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,6 @@ public class HotRodUserSessionEntity extends AbstractHotRodEntity {
|
||||||
@ProtoField(number = 12)
|
@ProtoField(number = 12)
|
||||||
public Long lastSessionRefresh;
|
public Long lastSessionRefresh;
|
||||||
|
|
||||||
@Basic(sortable = true)
|
|
||||||
@ProtoField(number = 13)
|
@ProtoField(number = 13)
|
||||||
public Long expiration;
|
public Long expiration;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -65,6 +66,7 @@ public class HotRodUserSessionTransaction<K> extends ConcurrentHashMapKeycloakTr
|
||||||
}
|
}
|
||||||
|
|
||||||
private MapAuthenticatedClientSessionEntity wrapClientSessionEntityToClientSessionAwareDelegate(MapAuthenticatedClientSessionEntity d) {
|
private MapAuthenticatedClientSessionEntity wrapClientSessionEntityToClientSessionAwareDelegate(MapAuthenticatedClientSessionEntity d) {
|
||||||
|
if (!clientSessionTransaction.exists(d.getId())) return null;
|
||||||
return new MapAuthenticatedClientSessionEntityDelegate(new HotRodAuthenticatedClientSessionEntityDelegateProvider(d) {
|
return new MapAuthenticatedClientSessionEntityDelegate(new HotRodAuthenticatedClientSessionEntityDelegateProvider(d) {
|
||||||
@Override
|
@Override
|
||||||
public MapAuthenticatedClientSessionEntity loadClientSessionFromDatabase() {
|
public MapAuthenticatedClientSessionEntity loadClientSessionFromDatabase() {
|
||||||
|
@ -82,13 +84,15 @@ public class HotRodUserSessionTransaction<K> extends ConcurrentHashMapKeycloakTr
|
||||||
Set<MapAuthenticatedClientSessionEntity> clientSessions = super.getAuthenticatedClientSessions();
|
Set<MapAuthenticatedClientSessionEntity> clientSessions = super.getAuthenticatedClientSessions();
|
||||||
return clientSessions == null ? null : clientSessions.stream()
|
return clientSessions == null ? null : clientSessions.stream()
|
||||||
.map(HotRodUserSessionTransaction.this::wrapClientSessionEntityToClientSessionAwareDelegate)
|
.map(HotRodUserSessionTransaction.this::wrapClientSessionEntityToClientSessionAwareDelegate)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<MapAuthenticatedClientSessionEntity> getAuthenticatedClientSession(String clientUUID) {
|
public Optional<MapAuthenticatedClientSessionEntity> getAuthenticatedClientSession(String clientUUID) {
|
||||||
return super.getAuthenticatedClientSession(clientUUID)
|
return super.getAuthenticatedClientSession(clientUUID)
|
||||||
.map(HotRodUserSessionTransaction.this::wrapClientSessionEntityToClientSessionAwareDelegate);
|
.map(HotRodUserSessionTransaction.this::wrapClientSessionEntityToClientSessionAwareDelegate)
|
||||||
|
.filter(Objects::nonNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -67,6 +67,11 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.infinispan</groupId>
|
||||||
|
<artifactId>infinispan-tasks-api</artifactId>
|
||||||
|
<version>${infinispan.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.model.infinispan;
|
||||||
|
|
||||||
|
import org.infinispan.commons.logging.Log;
|
||||||
|
import org.infinispan.commons.logging.LogFactory;
|
||||||
|
import org.infinispan.commons.time.TimeService;
|
||||||
|
import org.infinispan.expiration.ExpirationManager;
|
||||||
|
import org.infinispan.factories.GlobalComponentRegistry;
|
||||||
|
import org.infinispan.factories.impl.BasicComponentRegistry;
|
||||||
|
import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
|
import org.infinispan.tasks.ServerTask;
|
||||||
|
import org.infinispan.tasks.TaskContext;
|
||||||
|
import org.infinispan.tasks.TaskExecutionMode;
|
||||||
|
import org.infinispan.util.EmbeddedTimeService;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class InfinispanTimeServiceTask implements ServerTask<String> {
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog(InfinispanTimeServiceTask.class);
|
||||||
|
private TaskContext context = null;
|
||||||
|
private static int offset;
|
||||||
|
|
||||||
|
public InfinispanTimeServiceTask() {
|
||||||
|
log.info("InfinispanTimeServiceTask construction");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String call() {
|
||||||
|
EmbeddedCacheManager cacheManager = context.getCacheManager();
|
||||||
|
Map<String, Object> params = new HashMap();
|
||||||
|
if (this.context.getParameters().isPresent())
|
||||||
|
params = this.context.getParameters().get();
|
||||||
|
if (params.containsKey("timeService")) {
|
||||||
|
offset = (int) params.get("timeService");
|
||||||
|
|
||||||
|
// rewire the Time service
|
||||||
|
GlobalComponentRegistry cr = cacheManager.getGlobalComponentRegistry();
|
||||||
|
BasicComponentRegistry bcr = cr.getComponent(BasicComponentRegistry.class);
|
||||||
|
bcr.replaceComponent(TimeService.class.getName(), KEYCLOAK_TIME_SERVICE, true);
|
||||||
|
cr.rewire();
|
||||||
|
cr.rewireNamedRegistries();
|
||||||
|
|
||||||
|
// process expiration in all caches
|
||||||
|
cacheManager.getCacheNames().stream()
|
||||||
|
.map(cacheManager::getCache)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(cache -> cache.getAdvancedCache().getExpirationManager())
|
||||||
|
.forEach(ExpirationManager::processExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "InfinispanTimeServiceTask: Infinispan server time moved by " + offset + " seconds.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
log.info("getName() called");
|
||||||
|
return "InfinispanTimeServiceTask";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTaskContext(TaskContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TaskExecutionMode getExecutionMode() {
|
||||||
|
return TaskExecutionMode.ALL_NODES;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final TimeService KEYCLOAK_TIME_SERVICE = new EmbeddedTimeService() {
|
||||||
|
|
||||||
|
private long getCurrentTimeMillis() {
|
||||||
|
return System.currentTimeMillis() + (TimeUnit.SECONDS.toMillis(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long wallClockTime() {
|
||||||
|
return getCurrentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long time() {
|
||||||
|
return TimeUnit.MILLISECONDS.toNanos(getCurrentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant instant() {
|
||||||
|
return Instant.ofEpochMilli(getCurrentTimeMillis());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.rest;
|
package org.keycloak.testsuite.rest;
|
||||||
|
|
||||||
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.keycloak.http.HttpRequest;
|
import org.keycloak.http.HttpRequest;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
@ -45,6 +46,13 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.UserSessionSpi;
|
||||||
|
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||||
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
|
||||||
|
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.sessions.CrossDCLastSessionRefreshStoreFactory;
|
import org.keycloak.models.sessions.infinispan.changes.sessions.CrossDCLastSessionRefreshStoreFactory;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
||||||
|
@ -238,6 +246,18 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
public Map<String, String> setTimeOffset(Map<String, String> time) {
|
public Map<String, String> setTimeOffset(Map<String, String> time) {
|
||||||
int offset = Integer.parseInt(time.get("offset"));
|
int offset = Integer.parseInt(time.get("offset"));
|
||||||
|
|
||||||
|
// move time on Hot Rod server if present
|
||||||
|
// determine usage of Infinispan based on user sessions config
|
||||||
|
String userSessionProvider = Config.scope(UserSessionSpi.NAME, MapUserSessionProviderFactory.PROVIDER_ID, AbstractMapProviderFactory.CONFIG_STORAGE).get("provider");
|
||||||
|
if (Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE) && "hotrod".equals(userSessionProvider)) {
|
||||||
|
RemoteCache<Object, Object> scriptCache = session.getProvider(HotRodConnectionProvider.class).getRemoteCache(DefaultHotRodConnectionProviderFactory.SCRIPT_CACHE);
|
||||||
|
if (scriptCache != null) {
|
||||||
|
Map<String, Object> param = new HashMap<>();
|
||||||
|
param.put("timeService", offset);
|
||||||
|
scriptCache.execute("InfinispanTimeServiceTask", param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Time.setOffset(offset);
|
Time.setOffset(offset);
|
||||||
|
|
||||||
// Time offset was restarted
|
// Time offset was restarted
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.testsuite.model.infinispan.InfinispanTimeServiceTask
|
|
@ -327,6 +327,18 @@
|
||||||
</artifactItems>
|
</artifactItems>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>copy-testsuite-providers-to-base-testsuite</id>
|
||||||
|
<phase>generate-test-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includeGroupIds>org.keycloak.testsuite</includeGroupIds>
|
||||||
|
<includeArtifactIds>integration-arquillian-testsuite-providers</includeArtifactIds>
|
||||||
|
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
@ -957,6 +969,7 @@
|
||||||
<keycloak.authEventsStore.map.storage.provider>hotrod</keycloak.authEventsStore.map.storage.provider>
|
<keycloak.authEventsStore.map.storage.provider>hotrod</keycloak.authEventsStore.map.storage.provider>
|
||||||
<keycloak.singleUseObject.map.storage.provider>hotrod</keycloak.singleUseObject.map.storage.provider>
|
<keycloak.singleUseObject.map.storage.provider>hotrod</keycloak.singleUseObject.map.storage.provider>
|
||||||
<infinispan.version>${infinispan.version}</infinispan.version>
|
<infinispan.version>${infinispan.version}</infinispan.version>
|
||||||
|
<project.version>${project.version}</project.version>
|
||||||
<keycloak.testsuite.start-hotrod-container>${keycloak.testsuite.start-hotrod-container}</keycloak.testsuite.start-hotrod-container>
|
<keycloak.testsuite.start-hotrod-container>${keycloak.testsuite.start-hotrod-container}</keycloak.testsuite.start-hotrod-container>
|
||||||
<auth.server.quarkus.mapStorage.profile.config>hotrod</auth.server.quarkus.mapStorage.profile.config>
|
<auth.server.quarkus.mapStorage.profile.config>hotrod</auth.server.quarkus.mapStorage.profile.config>
|
||||||
<keycloak.globalLock.provider>hotrod</keycloak.globalLock.provider>
|
<keycloak.globalLock.provider>hotrod</keycloak.globalLock.provider>
|
||||||
|
|
|
@ -12,10 +12,11 @@ public class HotRodStoreTestEnricher {
|
||||||
|
|
||||||
public static final boolean HOT_ROD_START_CONTAINER = Boolean.parseBoolean(System.getProperty("keycloak.testsuite.start-hotrod-container", "false"));
|
public static final boolean HOT_ROD_START_CONTAINER = Boolean.parseBoolean(System.getProperty("keycloak.testsuite.start-hotrod-container", "false"));
|
||||||
|
|
||||||
private final InfinispanContainer hotRodContainer = new InfinispanContainer();
|
private InfinispanContainer hotRodContainer;
|
||||||
|
|
||||||
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
|
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
|
||||||
if (!HOT_ROD_START_CONTAINER) return;
|
if (!HOT_ROD_START_CONTAINER) return;
|
||||||
|
hotRodContainer = new InfinispanContainer();
|
||||||
hotRodContainer.start();
|
hotRodContainer.start();
|
||||||
|
|
||||||
// Add env variable, so it can be picked up by Keycloak
|
// Add env variable, so it can be picked up by Keycloak
|
||||||
|
@ -24,6 +25,6 @@ public class HotRodStoreTestEnricher {
|
||||||
|
|
||||||
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
|
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
|
||||||
if (!HOT_ROD_START_CONTAINER) return;
|
if (!HOT_ROD_START_CONTAINER) return;
|
||||||
hotRodContainer.stop();
|
if (hotRodContainer != null) hotRodContainer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,11 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.testsuite.arquillian.HotRodStoreTestEnricher;
|
import org.keycloak.testsuite.arquillian.HotRodStoreTestEnricher;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
import org.testcontainers.utility.MountableFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -51,6 +55,18 @@ public class InfinispanContainer extends GenericContainer<InfinispanContainer> {
|
||||||
withEnv("PASS", PASSWORD);
|
withEnv("PASS", PASSWORD);
|
||||||
withNetworkMode("host");
|
withNetworkMode("host");
|
||||||
|
|
||||||
|
Path dir = Path.of(Path.of("").toAbsolutePath() + "/target/lib");
|
||||||
|
String projectVersion = System.getProperty("project.version");
|
||||||
|
Path timeTaskPath;
|
||||||
|
try {
|
||||||
|
timeTaskPath = Files.find(dir, 1, (path, attr) -> path.toString()
|
||||||
|
.endsWith("integration-arquillian-testsuite-providers-" + projectVersion + ".jar")).findFirst().orElse(null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
MountableFile mountableFile = MountableFile.forHostPath(timeTaskPath, 0666);
|
||||||
|
withCopyFileToContainer(mountableFile, "/opt/infinispan/server/lib/integration-arquillian-testsuite-providers.jar");
|
||||||
|
|
||||||
withStartupTimeout(Duration.ofMinutes(5));
|
withStartupTimeout(Duration.ofMinutes(5));
|
||||||
waitingFor(Wait.forLogMessage(".*Infinispan Server.*started in.*", 1));
|
waitingFor(Wait.forLogMessage(".*Infinispan Server.*started in.*", 1));
|
||||||
}
|
}
|
||||||
|
|
|
@ -644,6 +644,7 @@ public abstract class AbstractKeycloakTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets time offset in seconds that will be added to Time.currentTime() and Time.currentTimeMillis() both for client and server.
|
* Sets time offset in seconds that will be added to Time.currentTime() and Time.currentTimeMillis() both for client and server.
|
||||||
|
* Moves time on the remote Infinispan server as well if the HotRod storage is used.
|
||||||
*
|
*
|
||||||
* @param offset
|
* @param offset
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.admin;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.EventStoreProvider;
|
||||||
|
import org.keycloak.events.EventStoreSpi;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.events.jpa.JpaEventStoreProviderFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class TimeOffsetTest extends AbstractAdminTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOffset() {
|
||||||
|
String realmId = adminClient.realm(REALM_NAME).toRepresentation().getId();
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealmByName(REALM_NAME);
|
||||||
|
realm.setEventsExpiration(5);
|
||||||
|
EventStoreProvider provider = session.getProvider(EventStoreProvider.class);
|
||||||
|
|
||||||
|
Event e = new Event();
|
||||||
|
e.setType(EventType.LOGIN);
|
||||||
|
e.setTime(Time.currentTimeMillis());
|
||||||
|
e.setRealmId(realmId);
|
||||||
|
provider.onEvent(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
EventStoreProvider provider = session.getProvider(EventStoreProvider.class);
|
||||||
|
assertEquals(1, provider.createQuery().realm(realmId).getResultStream().count());
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeOffset(5);
|
||||||
|
|
||||||
|
// legacy store requires manual trigger of expired events removal
|
||||||
|
String eventStoreProvider = testingClient.server().fetch(session -> Config.getProvider(EventStoreSpi.NAME), String.class);
|
||||||
|
if (eventStoreProvider.equals(JpaEventStoreProviderFactory.ID)) {
|
||||||
|
testingClient.testing().clearExpiredEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
EventStoreProvider provider = session.getProvider(EventStoreProvider.class);
|
||||||
|
assertEquals(0, provider.createQuery().realm(realmId).getResultStream().count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -179,6 +179,7 @@
|
||||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||||
<org.jboss.logging.provider>log4j</org.jboss.logging.provider>
|
<org.jboss.logging.provider>log4j</org.jboss.logging.provider>
|
||||||
<infinispan.version>${infinispan.version}</infinispan.version>
|
<infinispan.version>${infinispan.version}</infinispan.version>
|
||||||
|
<project.version>${project.version}</project.version>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
<properties>
|
<properties>
|
||||||
<property>
|
<property>
|
||||||
|
@ -208,6 +209,23 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-testsuite-providers-to-model-testsuite</id>
|
||||||
|
<phase>generate-test-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includeGroupIds>org.keycloak.testsuite</includeGroupIds>
|
||||||
|
<includeArtifactIds>integration-arquillian-testsuite-providers</includeArtifactIds>
|
||||||
|
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.model;
|
package org.keycloak.testsuite.model;
|
||||||
|
|
||||||
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
import org.keycloak.authorization.AuthorizationSpi;
|
import org.keycloak.authorization.AuthorizationSpi;
|
||||||
|
@ -45,6 +46,8 @@ import org.keycloak.models.DeploymentStateSpi;
|
||||||
import org.keycloak.models.UserLoginFailureSpi;
|
import org.keycloak.models.UserLoginFailureSpi;
|
||||||
import org.keycloak.models.UserSessionSpi;
|
import org.keycloak.models.UserSessionSpi;
|
||||||
import org.keycloak.models.UserSpi;
|
import org.keycloak.models.UserSpi;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
|
||||||
import org.keycloak.models.locking.GlobalLockProviderSpi;
|
import org.keycloak.models.locking.GlobalLockProviderSpi;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.PostMigrationEvent;
|
import org.keycloak.models.utils.PostMigrationEvent;
|
||||||
|
@ -63,7 +66,7 @@ import java.lang.management.LockInfo;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.lang.management.ThreadInfo;
|
import java.lang.management.ThreadInfo;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -81,7 +84,6 @@ import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -527,17 +529,17 @@ public abstract class KeycloakModelTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public final void createEnvironment() {
|
public final void createEnvironment() {
|
||||||
Time.setOffset(0);
|
setTimeOffset(0);
|
||||||
USE_DEFAULT_FACTORY = isUseSameKeycloakSessionFactoryForAllThreads();
|
USE_DEFAULT_FACTORY = isUseSameKeycloakSessionFactoryForAllThreads();
|
||||||
KeycloakModelUtils.runJobInTransaction(getFactory(), this::createEnvironment);
|
KeycloakModelUtils.runJobInTransaction(getFactory(), this::createEnvironment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public final void cleanEnvironment() {
|
public final void cleanEnvironment() {
|
||||||
Time.setOffset(0);
|
|
||||||
if (getFactory() == null) {
|
if (getFactory() == null) {
|
||||||
reinitializeKeycloakSessionFactory();
|
reinitializeKeycloakSessionFactory();
|
||||||
}
|
}
|
||||||
|
setTimeOffset(0);
|
||||||
KeycloakModelUtils.runJobInTransaction(getFactory(), this::cleanEnvironment);
|
KeycloakModelUtils.runJobInTransaction(getFactory(), this::cleanEnvironment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,4 +639,24 @@ public abstract class KeycloakModelTest {
|
||||||
return realm;
|
return realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves time on the Keycloak server as well as on the remote Infinispan server if the Infinispan is used.
|
||||||
|
* @param seconds time offset in seconds by which Keycloak (and Infinispan) server time is moved
|
||||||
|
*/
|
||||||
|
protected void setTimeOffset(int seconds) {
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// move time on Hot Rod server if present
|
||||||
|
HotRodConnectionProvider hotRodConnectionProvider = session.getProvider(HotRodConnectionProvider.class);
|
||||||
|
if (hotRodConnectionProvider != null) {
|
||||||
|
RemoteCache<Object, Object> scriptCache = hotRodConnectionProvider.getRemoteCache(DefaultHotRodConnectionProviderFactory.SCRIPT_CACHE);
|
||||||
|
if (scriptCache != null) {
|
||||||
|
Map<String, Object> param = new HashMap<>();
|
||||||
|
param.put("timeService", seconds);
|
||||||
|
Object returnFromTask = scriptCache.execute("InfinispanTimeServiceTask", param);
|
||||||
|
LOG.info(returnFromTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Time.setOffset(seconds);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import org.jboss.logging.Logger;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
import org.keycloak.migration.MigrationModel;
|
import org.keycloak.migration.MigrationModel;
|
||||||
import org.keycloak.migration.ModelVersion;
|
import org.keycloak.migration.ModelVersion;
|
||||||
|
@ -72,12 +71,12 @@ public class MigrationModelTest extends KeycloakModelTest {
|
||||||
Assert.assertEquals(currentVersion, m.getStoredVersion());
|
Assert.assertEquals(currentVersion, m.getStoredVersion());
|
||||||
Assert.assertEquals(m.getResourcesTag(), l.get(0).getId());
|
Assert.assertEquals(m.getResourcesTag(), l.get(0).getId());
|
||||||
|
|
||||||
Time.setOffset(-60000);
|
setTimeOffset(-60000);
|
||||||
|
|
||||||
session.getProvider(DeploymentStateProvider.class).getMigrationModel().setStoredVersion("6.0.0");
|
session.getProvider(DeploymentStateProvider.class).getMigrationModel().setStoredVersion("6.0.0");
|
||||||
em.flush();
|
em.flush();
|
||||||
|
|
||||||
Time.setOffset(0);
|
setTimeOffset(0);
|
||||||
|
|
||||||
l = em.createQuery("select m from MigrationModelEntity m ORDER BY m.updatedTime DESC", MigrationModelEntity.class).getResultList();
|
l = em.createQuery("select m from MigrationModelEntity m ORDER BY m.updatedTime DESC", MigrationModelEntity.class).getResultList();
|
||||||
Assert.assertEquals(2, l.size());
|
Assert.assertEquals(2, l.size());
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.model;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.EventStoreProvider;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@RequireProvider(EventStoreProvider.class)
|
||||||
|
public class TimeOffsetTest extends KeycloakModelTest {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel r = s.realms().createRealm("realm");
|
||||||
|
r.setDefaultRole(s.roles().addRealmRole(r, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + r.getName()));
|
||||||
|
r.setEventsExpiration(5);
|
||||||
|
realmId = r.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanEnvironment(KeycloakSession s) {
|
||||||
|
s.realms().removeRealm(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOffset() {
|
||||||
|
withRealm(realmId, (session, realmModel) -> {
|
||||||
|
EventStoreProvider provider = session.getProvider(EventStoreProvider.class);
|
||||||
|
|
||||||
|
Event e = new Event();
|
||||||
|
e.setType(EventType.LOGIN);
|
||||||
|
e.setRealmId(realmId);
|
||||||
|
e.setTime(Time.currentTimeMillis());
|
||||||
|
provider.onEvent(e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
withRealm(realmId, (session, realmModel) -> {
|
||||||
|
EventStoreProvider provider = session.getProvider(EventStoreProvider.class);
|
||||||
|
assertEquals(1, provider.createQuery().realm(realmId).getResultStream().count());
|
||||||
|
|
||||||
|
setTimeOffset(5);
|
||||||
|
|
||||||
|
// legacy store requires explicit expiration of expired events
|
||||||
|
ProviderFactory<EventStoreProvider> providerFactory = session.getKeycloakSessionFactory().getProviderFactory(EventStoreProvider.class);
|
||||||
|
if ("jpa".equals(providerFactory.getId())) {
|
||||||
|
provider.clearExpiredEvents();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
withRealm(realmId, (session, realmModel) -> {
|
||||||
|
EventStoreProvider provider = session.getProvider(EventStoreProvider.class);
|
||||||
|
assertEquals(0, provider.createQuery().realm(realmId).getResultStream().count());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,6 @@
|
||||||
package org.keycloak.testsuite.model.events;
|
package org.keycloak.testsuite.model.events;
|
||||||
|
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventStoreProvider;
|
import org.keycloak.events.EventStoreProvider;
|
||||||
|
@ -159,7 +158,7 @@ public class EventQueryTest extends KeycloakModelTest {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
Time.setOffset(10);
|
setTimeOffset(10);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
withRealm(realmId, (session, realm) -> {
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
@ -173,7 +172,7 @@ public class EventQueryTest extends KeycloakModelTest {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
Time.setOffset(0);
|
setTimeOffset(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ package org.keycloak.testsuite.model.session;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -72,7 +71,7 @@ public class AuthenticationSessionTest extends KeycloakModelTest {
|
||||||
ClientModel client = realm.getClientByClientId("test-app");
|
ClientModel client = realm.getClientByClientId("test-app");
|
||||||
return IntStream.range(0, 300)
|
return IntStream.range(0, 300)
|
||||||
.mapToObj(i -> {
|
.mapToObj(i -> {
|
||||||
Time.setOffset(i);
|
setTimeOffset(i);
|
||||||
return ras.createAuthenticationSession(client);
|
return ras.createAuthenticationSession(client);
|
||||||
})
|
})
|
||||||
.map(AuthenticationSessionModel::getTabId)
|
.map(AuthenticationSessionModel::getTabId)
|
||||||
|
@ -184,7 +183,7 @@ public class AuthenticationSessionTest extends KeycloakModelTest {
|
||||||
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSessionId.get());
|
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSessionId.get());
|
||||||
Assert.assertNotNull(rootAuthSession);
|
Assert.assertNotNull(rootAuthSession);
|
||||||
|
|
||||||
Time.setOffset(1900);
|
setTimeOffset(1900);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package org.keycloak.testsuite.model.session;
|
package org.keycloak.testsuite.model.session;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -67,9 +66,8 @@ public class UserSessionExpirationTest extends KeycloakModelTest {
|
||||||
|
|
||||||
assertThat(withRealm(realmId, (session, realm) -> session.sessions().getUserSession(realm, uSId)), notNullValue());
|
assertThat(withRealm(realmId, (session, realm) -> session.sessions().getUserSession(realm, uSId)), notNullValue());
|
||||||
|
|
||||||
Time.setOffset(5);
|
setTimeOffset(5);
|
||||||
|
|
||||||
assertThat(withRealm(realmId, (session, realm) -> session.sessions().getUserSession(realm, uSId)), nullValue());
|
assertThat(withRealm(realmId, (session, realm) -> session.sessions().getUserSession(realm, uSId)), nullValue());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -432,7 +432,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||||
|
|
||||||
for (int i = 0; i < USER_SESSION_COUNT; i++) {
|
for (int i = 0; i < USER_SESSION_COUNT; i++) {
|
||||||
// Having different offsets for each session (to ensure that lastSessionRefresh is also different)
|
// Having different offsets for each session (to ensure that lastSessionRefresh is also different)
|
||||||
Time.setOffset(i);
|
setTimeOffset(i);
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, "user1", "127.0.0.1", "form", true, null, null);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, user, "user1", "127.0.0.1", "form", true, null, null);
|
||||||
createClientSession(session, realmId, realm.getClientByClientId("test-app"), userSession, "http://redirect", "state");
|
createClientSession(session, realmId, realm.getClientByClientId("test-app"), userSession, "http://redirect", "state");
|
||||||
|
@ -464,6 +464,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -495,7 +496,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||||
persister.updateLastSessionRefreshes(realm, lastSessionRefresh, Collections.singleton(userSession1[0].getId()), true);
|
persister.updateLastSessionRefreshes(realm, lastSessionRefresh, Collections.singleton(userSession1[0].getId()), true);
|
||||||
|
|
||||||
// Increase time offset - 40 days
|
// Increase time offset - 40 days
|
||||||
Time.setOffset(3456000);
|
setTimeOffset(3456000);
|
||||||
try {
|
try {
|
||||||
// Run expiration thread
|
// Run expiration thread
|
||||||
persister.removeExpired(realm);
|
persister.removeExpired(realm);
|
||||||
|
@ -507,7 +508,7 @@ public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Cleanup
|
// Cleanup
|
||||||
Time.setOffset(0);
|
setTimeOffset(0);
|
||||||
session.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
session.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,6 @@ import org.hamcrest.Matchers;
|
||||||
import org.infinispan.client.hotrod.RemoteCache;
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -193,7 +192,7 @@ public class UserSessionProviderModelTest extends KeycloakModelTest {
|
||||||
clientSession.setTimestamp(1);
|
clientSession.setTimestamp(1);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Time.setOffset(1000);
|
setTimeOffset(1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -211,7 +210,7 @@ public class UserSessionProviderModelTest extends KeycloakModelTest {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
Time.setOffset(0);
|
setTimeOffset(0);
|
||||||
kcSession.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
kcSession.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
||||||
if (timer != null && timerTaskCtx != null) {
|
if (timer != null && timerTaskCtx != null) {
|
||||||
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.model.session;
|
package org.keycloak.testsuite.model.session;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
|
||||||
import org.infinispan.Cache;
|
import org.infinispan.Cache;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
|
@ -51,9 +50,7 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
@ -163,7 +160,7 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
// sessions are in persister too
|
// sessions are in persister too
|
||||||
Assert.assertEquals(3, persister.getUserSessionsCount(true));
|
Assert.assertEquals(3, persister.getUserSessionsCount(true));
|
||||||
|
|
||||||
Time.setOffset(300);
|
setTimeOffset(300);
|
||||||
log.infof("Set time offset to 300. Time is: %d", Time.currentTime());
|
log.infof("Set time offset to 300. Time is: %d", Time.currentTime());
|
||||||
|
|
||||||
// Set lastSessionRefresh to currentSession[0] to 0
|
// Set lastSessionRefresh to currentSession[0] to 0
|
||||||
|
@ -178,7 +175,7 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
int timeOffset = 1728000 + (i * 86400);
|
int timeOffset = 1728000 + (i * 86400);
|
||||||
|
|
||||||
RealmModel realm = session.realms().getRealm(realmId);
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
Time.setOffset(timeOffset);
|
setTimeOffset(timeOffset);
|
||||||
log.infof("Set time offset to %d. Time is: %d", timeOffset, Time.currentTime());
|
log.infof("Set time offset to %d. Time is: %d", timeOffset, Time.currentTime());
|
||||||
|
|
||||||
UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
|
UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
|
||||||
|
@ -192,7 +189,7 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
persister = session.getProvider(UserSessionPersisterProvider.class);
|
persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
// Increase timeOffset - 40 days
|
// Increase timeOffset - 40 days
|
||||||
Time.setOffset(3456000);
|
setTimeOffset(3456000);
|
||||||
log.infof("Set time offset to 3456000. Time is: %d", Time.currentTime());
|
log.infof("Set time offset to 3456000. Time is: %d", Time.currentTime());
|
||||||
|
|
||||||
// Expire and ensure that all sessions despite session0 were removed
|
// Expire and ensure that all sessions despite session0 were removed
|
||||||
|
@ -211,7 +208,7 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
Assert.assertEquals(1, persister.getUserSessionsCount(true));
|
Assert.assertEquals(1, persister.getUserSessionsCount(true));
|
||||||
|
|
||||||
// Expire everything and assert nothing found
|
// Expire everything and assert nothing found
|
||||||
Time.setOffset(7000000);
|
setTimeOffset(7000000);
|
||||||
|
|
||||||
persister.removeExpired(realm);
|
persister.removeExpired(realm);
|
||||||
});
|
});
|
||||||
|
@ -228,7 +225,7 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
});
|
});
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
Time.setOffset(0);
|
setTimeOffset(0);
|
||||||
kcSession.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
kcSession.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
@ -278,7 +275,7 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
persister = session.getProvider(UserSessionPersisterProvider.class);
|
persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
// Expire everything except offline client sessions
|
// Expire everything except offline client sessions
|
||||||
Time.setOffset(7000000);
|
setTimeOffset(7000000);
|
||||||
|
|
||||||
persister.removeExpired(realm);
|
persister.removeExpired(realm);
|
||||||
});
|
});
|
||||||
|
@ -300,7 +297,7 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
});
|
});
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
Time.setOffset(0);
|
setTimeOffset(0);
|
||||||
kcSession.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
kcSession.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
|
|
@ -60,7 +60,6 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cleanEnvironment(KeycloakSession s) {
|
public void cleanEnvironment(KeycloakSession s) {
|
||||||
Time.setOffset(0);
|
|
||||||
s.realms().removeRealm(realmId);
|
s.realms().removeRealm(realmId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +102,7 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
|
||||||
Assert.assertNotNull(notes);
|
Assert.assertNotNull(notes);
|
||||||
Assert.assertEquals("bar", notes.get("foo"));
|
Assert.assertEquals("bar", notes.get("foo"));
|
||||||
|
|
||||||
Time.setOffset(70);
|
setTimeOffset(70);
|
||||||
|
|
||||||
notes = singleUseObjectProvider.get(key.serializeKey());
|
notes = singleUseObjectProvider.get(key.serializeKey());
|
||||||
Assert.assertNull(notes);
|
Assert.assertNull(notes);
|
||||||
|
@ -154,7 +153,7 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
|
||||||
Map<String, String> actualNotes = singleUseStore.get(key);
|
Map<String, String> actualNotes = singleUseStore.get(key);
|
||||||
assertThat(actualNotes, Matchers.anEmptyMap());
|
assertThat(actualNotes, Matchers.anEmptyMap());
|
||||||
|
|
||||||
Time.setOffset(70);
|
setTimeOffset(70);
|
||||||
|
|
||||||
Assert.assertNull(singleUseStore.get(key));
|
Assert.assertNull(singleUseStore.get(key));
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue