KEYCLOAK-16378 User / client session map store
Co-authored-by: Martin Kanis <mkanis@redhat.com> Co-authored-by: Hynek Mlnarik <hmlnarik@redhat.com>
This commit is contained in:
parent
5c4753ef20
commit
515bfb5064
81 changed files with 4945 additions and 1406 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -160,7 +160,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
declare -A PARAMS TESTGROUP
|
declare -A PARAMS TESTGROUP
|
||||||
PARAMS["quarkus"]="-Pauth-server-quarkus"
|
PARAMS["quarkus"]="-Pauth-server-quarkus"
|
||||||
PARAMS["undertow-map"]="-Pauth-server-undertow -Dkeycloak.client.provider=map -Dkeycloak.group.provider=map -Dkeycloak.role.provider=map -Dkeycloak.authSession.provider=map -Dkeycloak.user.provider=map -Dkeycloak.clientScope.provider=map -Dkeycloak.realm.provider=map -Dkeycloak.authorization.provider=map"
|
PARAMS["undertow-map"]="-Pauth-server-undertow -Dkeycloak.client.provider=map -Dkeycloak.group.provider=map -Dkeycloak.role.provider=map -Dkeycloak.authSession.provider=map -Dkeycloak.userSession.provider=map -Dkeycloak.loginFailure.provider=map -Dkeycloak.user.provider=map -Dkeycloak.clientScope.provider=map -Dkeycloak.realm.provider=map -Dkeycloak.authorization.provider=map"
|
||||||
PARAMS["wildfly"]="-Pauth-server-wildfly"
|
PARAMS["wildfly"]="-Pauth-server-wildfly"
|
||||||
TESTGROUP["group1"]="-Dtest=!**.crossdc.**,!**.cluster.**,%regex[org.keycloak.testsuite.(a[abc]|ad[a-l]|[^a-q]).*]" # Tests alphabetically before admin tests and those after "r"
|
TESTGROUP["group1"]="-Dtest=!**.crossdc.**,!**.cluster.**,%regex[org.keycloak.testsuite.(a[abc]|ad[a-l]|[^a-q]).*]" # Tests alphabetically before admin tests and those after "r"
|
||||||
TESTGROUP["group2"]="-Dtest=!**.crossdc.**,!**.cluster.**,%regex[org.keycloak.testsuite.(ad[^a-l]|a[^a-d]|b).*]" # Admin tests and those starting with "b"
|
TESTGROUP["group2"]="-Dtest=!**.crossdc.**,!**.cluster.**,%regex[org.keycloak.testsuite.(ad[^a-l]|a[^a-d]|b).*]" # Admin tests and those starting with "b"
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class StackUtil {
|
||||||
return getShortStackTrace("\n ");
|
return getShortStackTrace("\n ");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern IGNORED = Pattern.compile("sun\\.|java\\.(lang|util|stream)\\.|org\\.jboss\\.(arquillian|logging).|org.apache.maven.surefire");
|
private static final Pattern IGNORED = Pattern.compile("sun\\.|java\\.(lang|util|stream)\\.|org\\.jboss\\.(arquillian|logging).|org.apache.maven.surefire|org\\.junit\\.|org.keycloak.testsuite.model.KeycloakModelTest\\.");
|
||||||
private static final StringBuilder EMPTY = new StringBuilder(0);
|
private static final StringBuilder EMPTY = new StringBuilder(0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -114,7 +114,7 @@ public class Config {
|
||||||
@Override
|
@Override
|
||||||
public Integer getInt(String key, Integer defaultValue) {
|
public Integer getInt(String key, Integer defaultValue) {
|
||||||
String v = get(key, null);
|
String v = get(key, null);
|
||||||
return v != null ? Integer.parseInt(v) : defaultValue;
|
return v != null ? Integer.valueOf(v) : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -125,7 +125,7 @@ public class Config {
|
||||||
@Override
|
@Override
|
||||||
public Long getLong(String key, Long defaultValue) {
|
public Long getLong(String key, Long defaultValue) {
|
||||||
String v = get(key, null);
|
String v = get(key, null);
|
||||||
return v != null ? Long.parseLong(v) : defaultValue;
|
return v != null ? Long.valueOf(v) : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -137,7 +137,7 @@ public class Config {
|
||||||
public Boolean getBoolean(String key, Boolean defaultValue) {
|
public Boolean getBoolean(String key, Boolean defaultValue) {
|
||||||
String v = get(key, null);
|
String v = get(key, null);
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
return Boolean.parseBoolean(v);
|
return Boolean.valueOf(v);
|
||||||
} else {
|
} else {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return null;
|
return entity.getId().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.UserLoginFailureProvider;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserLoginFailureModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.Tasks;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
|
import org.keycloak.models.sessions.infinispan.events.RemoveAllUserLoginFailuresEvent;
|
||||||
|
import org.keycloak.models.sessions.infinispan.events.SessionEventsSenderTransaction;
|
||||||
|
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.Mappers;
|
||||||
|
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
|
||||||
|
import org.keycloak.models.sessions.infinispan.util.FuturesHelper;
|
||||||
|
import org.keycloak.models.sessions.infinispan.util.SessionTimeouts;
|
||||||
|
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class InfinispanUserLoginFailureProvider implements UserLoginFailureProvider {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(InfinispanUserLoginFailureProvider.class);
|
||||||
|
|
||||||
|
protected final KeycloakSession session;
|
||||||
|
|
||||||
|
|
||||||
|
protected final Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache;
|
||||||
|
protected final InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> loginFailuresTx;
|
||||||
|
protected final SessionEventsSenderTransaction clusterEventsSenderTx;
|
||||||
|
|
||||||
|
public InfinispanUserLoginFailureProvider(KeycloakSession session,
|
||||||
|
RemoteCacheInvoker remoteCacheInvoker,
|
||||||
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache) {
|
||||||
|
this.session = session;
|
||||||
|
this.loginFailureCache = loginFailureCache;
|
||||||
|
this.loginFailuresTx = new InfinispanChangelogBasedTransaction<>(session, loginFailureCache, remoteCacheInvoker, SessionTimeouts::getLoginFailuresLifespanMs, SessionTimeouts::getLoginFailuresMaxIdleMs);
|
||||||
|
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
|
||||||
|
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(clusterEventsSenderTx);
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(loginFailuresTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
log.tracef("getUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
|
||||||
|
|
||||||
|
LoginFailureKey key = new LoginFailureKey(realm.getId(), userId);
|
||||||
|
LoginFailureEntity entity = getLoginFailureEntity(key);
|
||||||
|
return wrap(key, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
log.tracef("addUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
|
||||||
|
|
||||||
|
LoginFailureKey key = new LoginFailureKey(realm.getId(), userId);
|
||||||
|
LoginFailureEntity entity = new LoginFailureEntity();
|
||||||
|
entity.setRealmId(realm.getId());
|
||||||
|
entity.setUserId(userId);
|
||||||
|
|
||||||
|
SessionUpdateTask<LoginFailureEntity> createLoginFailureTask = Tasks.addIfAbsentSync();
|
||||||
|
loginFailuresTx.addTask(key, createLoginFailureTask, entity, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||||
|
|
||||||
|
return wrap(key, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
log.tracef("removeUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
|
||||||
|
|
||||||
|
SessionUpdateTask<LoginFailureEntity> removeTask = Tasks.removeSync();
|
||||||
|
loginFailuresTx.addTask(new LoginFailureKey(realm.getId(), userId), removeTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAllUserLoginFailures(RealmModel realm) {
|
||||||
|
log.tracef("removeAllUserLoginFailures(%s)%s", realm, getShortStackTrace());
|
||||||
|
|
||||||
|
clusterEventsSenderTx.addEvent(
|
||||||
|
RemoveAllUserLoginFailuresEvent.createEvent(RemoveAllUserLoginFailuresEvent.class, InfinispanUserLoginFailureProviderFactory.REMOVE_ALL_LOGIN_FAILURES_EVENT, session, realm.getId(), true),
|
||||||
|
ClusterProvider.DCNotify.LOCAL_DC_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeAllLocalUserLoginFailuresEvent(String realmId) {
|
||||||
|
log.tracef("removeAllLocalUserLoginFailuresEvent(%s)%s", realmId, getShortStackTrace());
|
||||||
|
|
||||||
|
FuturesHelper futures = new FuturesHelper();
|
||||||
|
|
||||||
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> localCache = CacheDecorators.localCache(loginFailureCache);
|
||||||
|
|
||||||
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
|
||||||
|
|
||||||
|
localCacheStoreIgnore
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(UserLoginFailurePredicate.create(realmId))
|
||||||
|
.map(Mappers.loginFailureId())
|
||||||
|
.forEach(loginFailureKey -> {
|
||||||
|
// Remove loginFailure from remoteCache too. Use removeAsync for better perf
|
||||||
|
Future future = localCache.removeAsync(loginFailureKey);
|
||||||
|
futures.addTask(future);
|
||||||
|
});
|
||||||
|
|
||||||
|
futures.waitForAllToFinish();
|
||||||
|
|
||||||
|
log.debugf("Removed %d login failures in realm %s", futures.size(), realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
|
||||||
|
return entity != null ? new UserLoginFailureAdapter(this, key, entity) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoginFailureEntity getLoginFailureEntity(LoginFailureKey key) {
|
||||||
|
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> tx = getLoginFailuresTx();
|
||||||
|
SessionEntityWrapper<LoginFailureEntity> entityWrapper = tx.get(key);
|
||||||
|
return entityWrapper==null ? null : entityWrapper.getEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> getLoginFailuresTx() {
|
||||||
|
return loginFailuresTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
|
import org.infinispan.persistence.remote.RemoteStore;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
|
import org.keycloak.models.UserLoginFailureProvider;
|
||||||
|
import org.keycloak.models.UserLoginFailureProviderFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
|
import org.keycloak.models.sessions.infinispan.events.AbstractUserSessionClusterListener;
|
||||||
|
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
||||||
|
import org.keycloak.models.sessions.infinispan.events.RemoveAllUserLoginFailuresEvent;
|
||||||
|
import org.keycloak.models.sessions.infinispan.initializer.InfinispanCacheInitializer;
|
||||||
|
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
|
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheSessionListener;
|
||||||
|
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheSessionsLoader;
|
||||||
|
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||||
|
import org.keycloak.models.sessions.infinispan.util.SessionTimeouts;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.models.utils.PostMigrationEvent;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class InfinispanUserLoginFailureProviderFactory implements UserLoginFailureProviderFactory {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(InfinispanUserLoginFailureProviderFactory.class);
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "infinispan";
|
||||||
|
|
||||||
|
public static final String REALM_REMOVED_SESSION_EVENT = "REALM_REMOVED_EVENT_SESSIONS";
|
||||||
|
|
||||||
|
public static final String REMOVE_ALL_LOGIN_FAILURES_EVENT = "REMOVE_ALL_LOGIN_FAILURES_EVENT";
|
||||||
|
|
||||||
|
private Config.Scope config;
|
||||||
|
|
||||||
|
private RemoteCacheInvoker remoteCacheInvoker;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserLoginFailureProvider create(KeycloakSession session) {
|
||||||
|
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
||||||
|
|
||||||
|
return new InfinispanUserLoginFailureProvider(session, remoteCacheInvoker, loginFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(final KeycloakSessionFactory factory) {
|
||||||
|
this.remoteCacheInvoker = new RemoteCacheInvoker();
|
||||||
|
|
||||||
|
factory.register(event -> {
|
||||||
|
if (event instanceof PostMigrationEvent) {
|
||||||
|
KeycloakModelUtils.runJobInTransaction(factory, (KeycloakSession session) -> {
|
||||||
|
checkRemoteCaches(session);
|
||||||
|
registerClusterListeners(session);
|
||||||
|
loadLoginFailuresFromRemoteCaches(session);
|
||||||
|
});
|
||||||
|
} else if (event instanceof UserModel.UserRemovedEvent) {
|
||||||
|
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
|
||||||
|
|
||||||
|
UserLoginFailureProvider provider = userRemovedEvent.getKeycloakSession().getProvider(UserLoginFailureProvider.class, getId());
|
||||||
|
provider.removeUserLoginFailure(userRemovedEvent.getRealm(), userRemovedEvent.getUser().getId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void registerClusterListeners(KeycloakSession session) {
|
||||||
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
|
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||||
|
|
||||||
|
cluster.registerListener(REALM_REMOVED_SESSION_EVENT,
|
||||||
|
new AbstractUserSessionClusterListener<RealmRemovedSessionEvent, UserLoginFailureProvider>(sessionFactory, UserLoginFailureProvider.class) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void eventReceived(KeycloakSession session, UserLoginFailureProvider provider, RealmRemovedSessionEvent sessionEvent) {
|
||||||
|
if (provider instanceof InfinispanUserLoginFailureProvider) {
|
||||||
|
((InfinispanUserLoginFailureProvider) provider).removeAllLocalUserLoginFailuresEvent(sessionEvent.getRealmId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster.registerListener(REMOVE_ALL_LOGIN_FAILURES_EVENT,
|
||||||
|
new AbstractUserSessionClusterListener<RemoveAllUserLoginFailuresEvent, UserLoginFailureProvider>(sessionFactory, UserLoginFailureProvider.class) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void eventReceived(KeycloakSession session, UserLoginFailureProvider provider, RemoveAllUserLoginFailuresEvent sessionEvent) {
|
||||||
|
if (provider instanceof InfinispanUserLoginFailureProvider) {
|
||||||
|
((InfinispanUserLoginFailureProvider) provider).removeAllLocalUserLoginFailuresEvent(sessionEvent.getRealmId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debug("Registered cluster listeners");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void checkRemoteCaches(KeycloakSession session) {
|
||||||
|
InfinispanConnectionProvider ispn = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
|
|
||||||
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailuresCache = ispn.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
||||||
|
checkRemoteCache(session, loginFailuresCache, (RealmModel realm) ->
|
||||||
|
Time.toMillis(realm.getMaxDeltaTimeSeconds()), SessionTimeouts::getLoginFailuresLifespanMs, SessionTimeouts::getLoginFailuresMaxIdleMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <K, V extends SessionEntity> RemoteCache checkRemoteCache(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> ispnCache, RemoteCacheInvoker.MaxIdleTimeLoader maxIdleLoader,
|
||||||
|
BiFunction<RealmModel, V, Long> lifespanMsLoader, BiFunction<RealmModel, V, Long> maxIdleTimeMsLoader) {
|
||||||
|
Set<RemoteStore> remoteStores = InfinispanUtil.getRemoteStores(ispnCache);
|
||||||
|
|
||||||
|
if (remoteStores.isEmpty()) {
|
||||||
|
log.debugf("No remote store configured for cache '%s'", ispnCache.getName());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
log.infof("Remote store configured for cache '%s'", ispnCache.getName());
|
||||||
|
|
||||||
|
RemoteCache<K, SessionEntityWrapper<V>> remoteCache = (RemoteCache) remoteStores.iterator().next().getRemoteCache();
|
||||||
|
|
||||||
|
if (remoteCache == null) {
|
||||||
|
throw new IllegalStateException("No remote cache available for the infinispan cache: " + ispnCache.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteCacheInvoker.addRemoteCache(ispnCache.getName(), remoteCache, maxIdleLoader);
|
||||||
|
|
||||||
|
RemoteCacheSessionListener hotrodListener = RemoteCacheSessionListener.createListener(session, ispnCache, remoteCache, lifespanMsLoader, maxIdleTimeMsLoader);
|
||||||
|
remoteCache.addClientListener(hotrodListener);
|
||||||
|
return remoteCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max count of worker errors. Initialization will end with exception when this number is reached
|
||||||
|
private int getMaxErrors() {
|
||||||
|
return config.getInt("maxErrors", 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count of sessions to be computed in each segment
|
||||||
|
private int getSessionsPerSegment() {
|
||||||
|
return config.getInt("sessionsPerSegment", 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadLoginFailuresFromRemoteCaches(KeycloakSession session) {
|
||||||
|
for (String cacheName : remoteCacheInvoker.getRemoteCacheNames()) {
|
||||||
|
loadLoginFailuresFromRemoteCaches(session.getKeycloakSessionFactory(), cacheName, getSessionsPerSegment(), getMaxErrors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadLoginFailuresFromRemoteCaches(final KeycloakSessionFactory sessionFactory, String cacheName, final int sessionsPerSegment, final int maxErrors) {
|
||||||
|
log.debugf("Check pre-loading sessions from remote cache '%s'", cacheName);
|
||||||
|
|
||||||
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
|
Cache<String, Serializable> workCache = connections.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
|
||||||
|
|
||||||
|
InfinispanCacheInitializer initializer = new InfinispanCacheInitializer(sessionFactory, workCache,
|
||||||
|
new RemoteCacheSessionsLoader(cacheName, sessionsPerSegment), "remoteCacheLoad::" + cacheName, sessionsPerSegment, maxErrors);
|
||||||
|
|
||||||
|
initializer.initCache();
|
||||||
|
initializer.loadSessions();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debugf("Pre-loading login failures from remote cache '%s' finished", cacheName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,6 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
import org.keycloak.models.OfflineUserSessionModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserLoginFailureModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.UserSessionProvider;
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
@ -46,17 +45,13 @@ import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedT
|
||||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
||||||
import org.keycloak.models.sessions.infinispan.events.RemoveAllUserLoginFailuresEvent;
|
|
||||||
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
|
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
|
||||||
import org.keycloak.models.sessions.infinispan.events.SessionEventsSenderTransaction;
|
import org.keycloak.models.sessions.infinispan.events.SessionEventsSenderTransaction;
|
||||||
import org.keycloak.models.sessions.infinispan.stream.Comparators;
|
import org.keycloak.models.sessions.infinispan.stream.Comparators;
|
||||||
import org.keycloak.models.sessions.infinispan.stream.Mappers;
|
import org.keycloak.models.sessions.infinispan.stream.Mappers;
|
||||||
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
|
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
|
||||||
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
|
|
||||||
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
|
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
|
||||||
import org.keycloak.models.sessions.infinispan.util.FuturesHelper;
|
import org.keycloak.models.sessions.infinispan.util.FuturesHelper;
|
||||||
import org.keycloak.models.sessions.infinispan.util.InfinispanKeyGenerator;
|
import org.keycloak.models.sessions.infinispan.util.InfinispanKeyGenerator;
|
||||||
|
@ -95,13 +90,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache;
|
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache;
|
||||||
protected final Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache;
|
protected final Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache;
|
||||||
protected final Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionCache;
|
protected final Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionCache;
|
||||||
protected final Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache;
|
|
||||||
|
|
||||||
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx;
|
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx;
|
||||||
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx;
|
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx;
|
||||||
protected final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionTx;
|
protected final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionTx;
|
||||||
protected final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> offlineClientSessionTx;
|
protected final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> offlineClientSessionTx;
|
||||||
protected final InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> loginFailuresTx;
|
|
||||||
|
|
||||||
protected final SessionEventsSenderTransaction clusterEventsSenderTx;
|
protected final SessionEventsSenderTransaction clusterEventsSenderTx;
|
||||||
|
|
||||||
|
@ -121,23 +114,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache,
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache,
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache,
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache,
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache,
|
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache,
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionCache,
|
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionCache) {
|
||||||
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache) {
|
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
|
||||||
this.sessionCache = sessionCache;
|
this.sessionCache = sessionCache;
|
||||||
this.clientSessionCache = clientSessionCache;
|
this.clientSessionCache = clientSessionCache;
|
||||||
this.offlineSessionCache = offlineSessionCache;
|
this.offlineSessionCache = offlineSessionCache;
|
||||||
this.offlineClientSessionCache = offlineClientSessionCache;
|
this.offlineClientSessionCache = offlineClientSessionCache;
|
||||||
this.loginFailureCache = loginFailureCache;
|
|
||||||
|
|
||||||
this.sessionTx = new InfinispanChangelogBasedTransaction<>(session, sessionCache, remoteCacheInvoker, SessionTimeouts::getUserSessionLifespanMs, SessionTimeouts::getUserSessionMaxIdleMs);
|
this.sessionTx = new InfinispanChangelogBasedTransaction<>(session, sessionCache, remoteCacheInvoker, SessionTimeouts::getUserSessionLifespanMs, SessionTimeouts::getUserSessionMaxIdleMs);
|
||||||
this.offlineSessionTx = new InfinispanChangelogBasedTransaction<>(session, offlineSessionCache, remoteCacheInvoker, SessionTimeouts::getOfflineSessionLifespanMs, SessionTimeouts::getOfflineSessionMaxIdleMs);
|
this.offlineSessionTx = new InfinispanChangelogBasedTransaction<>(session, offlineSessionCache, remoteCacheInvoker, SessionTimeouts::getOfflineSessionLifespanMs, SessionTimeouts::getOfflineSessionMaxIdleMs);
|
||||||
this.clientSessionTx = new InfinispanChangelogBasedTransaction<>(session, clientSessionCache, remoteCacheInvoker, SessionTimeouts::getClientSessionLifespanMs, SessionTimeouts::getClientSessionMaxIdleMs);
|
this.clientSessionTx = new InfinispanChangelogBasedTransaction<>(session, clientSessionCache, remoteCacheInvoker, SessionTimeouts::getClientSessionLifespanMs, SessionTimeouts::getClientSessionMaxIdleMs);
|
||||||
this.offlineClientSessionTx = new InfinispanChangelogBasedTransaction<>(session, offlineClientSessionCache, remoteCacheInvoker, SessionTimeouts::getOfflineClientSessionLifespanMs, SessionTimeouts::getOfflineClientSessionMaxIdleMs);
|
this.offlineClientSessionTx = new InfinispanChangelogBasedTransaction<>(session, offlineClientSessionCache, remoteCacheInvoker, SessionTimeouts::getOfflineClientSessionLifespanMs, SessionTimeouts::getOfflineClientSessionMaxIdleMs);
|
||||||
|
|
||||||
this.loginFailuresTx = new InfinispanChangelogBasedTransaction<>(session, loginFailureCache, remoteCacheInvoker, SessionTimeouts::getLoginFailuresLifespanMs, SessionTimeouts::getLoginFailuresMaxIdleMs);
|
|
||||||
|
|
||||||
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
|
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
|
||||||
|
|
||||||
this.lastSessionRefreshStore = lastSessionRefreshStore;
|
this.lastSessionRefreshStore = lastSessionRefreshStore;
|
||||||
|
@ -151,7 +140,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
session.getTransactionManager().enlistAfterCompletion(offlineSessionTx);
|
session.getTransactionManager().enlistAfterCompletion(offlineSessionTx);
|
||||||
session.getTransactionManager().enlistAfterCompletion(clientSessionTx);
|
session.getTransactionManager().enlistAfterCompletion(clientSessionTx);
|
||||||
session.getTransactionManager().enlistAfterCompletion(offlineClientSessionTx);
|
session.getTransactionManager().enlistAfterCompletion(offlineClientSessionTx);
|
||||||
session.getTransactionManager().enlistAfterCompletion(loginFailuresTx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Cache<String, SessionEntityWrapper<UserSessionEntity>> getCache(boolean offline) {
|
protected Cache<String, SessionEntityWrapper<UserSessionEntity>> getCache(boolean offline) {
|
||||||
|
@ -182,6 +170,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
return persisterLastSessionRefreshStore;
|
return persisterLastSessionRefreshStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
||||||
final UUID clientSessionId = keyGenerator.generateKeyUUID(session, clientSessionCache);
|
final UUID clientSessionId = keyGenerator.generateKeyUUID(session, clientSessionCache);
|
||||||
|
@ -528,72 +521,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
log.debugf("Removed %d sessions in realm %s. Offline: %b", (Object) userSessionsSize.get(), realmId, offline);
|
log.debugf("Removed %d sessions in realm %s. Offline: %b", (Object) userSessionsSize.get(), realmId, offline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId) {
|
|
||||||
LoginFailureKey key = new LoginFailureKey(realm.getId(), userId);
|
|
||||||
LoginFailureEntity entity = getLoginFailureEntity(key);
|
|
||||||
return wrap(key, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LoginFailureEntity getLoginFailureEntity(LoginFailureKey key) {
|
|
||||||
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> tx = getLoginFailuresTx();
|
|
||||||
SessionEntityWrapper<LoginFailureEntity> entityWrapper = tx.get(key);
|
|
||||||
return entityWrapper==null ? null : entityWrapper.getEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId) {
|
|
||||||
LoginFailureKey key = new LoginFailureKey(realm.getId(), userId);
|
|
||||||
LoginFailureEntity entity = new LoginFailureEntity();
|
|
||||||
entity.setRealmId(realm.getId());
|
|
||||||
entity.setUserId(userId);
|
|
||||||
|
|
||||||
SessionUpdateTask<LoginFailureEntity> createLoginFailureTask = Tasks.addIfAbsentSync();
|
|
||||||
loginFailuresTx.addTask(key, createLoginFailureTask, entity, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
|
||||||
|
|
||||||
return wrap(key, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeUserLoginFailure(RealmModel realm, String userId) {
|
|
||||||
SessionUpdateTask<LoginFailureEntity> removeTask = Tasks.removeSync();
|
|
||||||
loginFailuresTx.addTask(new LoginFailureKey(realm.getId(), userId), removeTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAllUserLoginFailures(RealmModel realm) {
|
|
||||||
clusterEventsSenderTx.addEvent(
|
|
||||||
RemoveAllUserLoginFailuresEvent.createEvent(RemoveAllUserLoginFailuresEvent.class, InfinispanUserSessionProviderFactory.REMOVE_ALL_LOGIN_FAILURES_EVENT, session, realm.getId(), true),
|
|
||||||
ClusterProvider.DCNotify.LOCAL_DC_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onRemoveAllUserLoginFailuresEvent(String realmId) {
|
|
||||||
removeAllLocalUserLoginFailuresEvent(realmId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeAllLocalUserLoginFailuresEvent(String realmId) {
|
|
||||||
FuturesHelper futures = new FuturesHelper();
|
|
||||||
|
|
||||||
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> localCache = CacheDecorators.localCache(loginFailureCache);
|
|
||||||
|
|
||||||
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
|
|
||||||
|
|
||||||
localCacheStoreIgnore
|
|
||||||
.entrySet()
|
|
||||||
.stream()
|
|
||||||
.filter(UserLoginFailurePredicate.create(realmId))
|
|
||||||
.map(Mappers.loginFailureId())
|
|
||||||
.forEach(loginFailureKey -> {
|
|
||||||
// Remove loginFailure from remoteCache too. Use removeAsync for better perf
|
|
||||||
Future future = localCache.removeAsync(loginFailureKey);
|
|
||||||
futures.addTask(future);
|
|
||||||
});
|
|
||||||
|
|
||||||
futures.waitForAllToFinish();
|
|
||||||
|
|
||||||
log.debugf("Removed %d login failures in realm %s", futures.size(), realmId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRealmRemoved(RealmModel realm) {
|
public void onRealmRemoved(RealmModel realm) {
|
||||||
// Don't send message to all DCs, just to all cluster nodes in current DC. The remoteCache will notify client listeners for removed userSessions.
|
// Don't send message to all DCs, just to all cluster nodes in current DC. The remoteCache will notify client listeners for removed userSessions.
|
||||||
|
@ -610,7 +537,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
protected void onRealmRemovedEvent(String realmId) {
|
protected void onRealmRemovedEvent(String realmId) {
|
||||||
removeLocalUserSessions(realmId, true);
|
removeLocalUserSessions(realmId, true);
|
||||||
removeLocalUserSessions(realmId, false);
|
removeLocalUserSessions(realmId, false);
|
||||||
removeAllLocalUserLoginFailuresEvent(realmId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -633,8 +559,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
removeUserSessions(realm, user, true);
|
removeUserSessions(realm, user, true);
|
||||||
removeUserSessions(realm, user, false);
|
removeUserSessions(realm, user, false);
|
||||||
|
|
||||||
removeUserLoginFailure(realm, user.getId());
|
|
||||||
|
|
||||||
UserSessionPersisterProvider persisterProvider = session.getProvider(UserSessionPersisterProvider.class);
|
UserSessionPersisterProvider persisterProvider = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
if (persisterProvider != null) {
|
if (persisterProvider != null) {
|
||||||
persisterProvider.onUserRemoved(realm, user);
|
persisterProvider.onUserRemoved(realm, user);
|
||||||
|
@ -653,10 +577,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
userSessionUpdateTx.addTask(sessionEntity.getId(), removeTask);
|
userSessionUpdateTx.addTask(sessionEntity.getId(), removeTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> getLoginFailuresTx() {
|
|
||||||
return loginFailuresTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
|
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity 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);
|
||||||
|
@ -669,10 +589,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
|
|
||||||
return entity != null ? new UserLoginFailureAdapter(this, key, entity) : 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;
|
||||||
|
|
|
@ -42,14 +42,11 @@ import org.keycloak.models.sessions.infinispan.initializer.DBLockBasedCacheIniti
|
||||||
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.events.AbstractUserSessionClusterListener;
|
import org.keycloak.models.sessions.infinispan.events.AbstractUserSessionClusterListener;
|
||||||
import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
|
import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
|
||||||
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
||||||
import org.keycloak.models.sessions.infinispan.events.RemoveAllUserLoginFailuresEvent;
|
|
||||||
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
|
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.InfinispanCacheInitializer;
|
import org.keycloak.models.sessions.infinispan.initializer.InfinispanCacheInitializer;
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
|
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
|
||||||
|
@ -81,8 +78,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
|
|
||||||
public static final String REMOVE_USER_SESSIONS_EVENT = "REMOVE_USER_SESSIONS_EVENT";
|
public static final String REMOVE_USER_SESSIONS_EVENT = "REMOVE_USER_SESSIONS_EVENT";
|
||||||
|
|
||||||
public static final String REMOVE_ALL_LOGIN_FAILURES_EVENT = "REMOVE_ALL_LOGIN_FAILURES_EVENT";
|
|
||||||
|
|
||||||
private Config.Scope config;
|
private Config.Scope config;
|
||||||
|
|
||||||
private RemoteCacheInvoker remoteCacheInvoker;
|
private RemoteCacheInvoker remoteCacheInvoker;
|
||||||
|
@ -98,11 +93,9 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME);
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = connections.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME);
|
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = connections.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME);
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME);
|
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME);
|
||||||
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
|
||||||
|
|
||||||
return new InfinispanUserSessionProvider(session, remoteCacheInvoker, lastSessionRefreshStore, offlineLastSessionRefreshStore,
|
return new InfinispanUserSessionProvider(session, remoteCacheInvoker, lastSessionRefreshStore, offlineLastSessionRefreshStore,
|
||||||
persisterLastSessionRefreshStore, keyGenerator,
|
persisterLastSessionRefreshStore, keyGenerator, cache, offlineSessionsCache, clientSessionCache, offlineClientSessionsCache);
|
||||||
cache, offlineSessionsCache, clientSessionCache, offlineClientSessionsCache, loginFailures);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -112,7 +105,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(final KeycloakSessionFactory factory) {
|
public void postInit(final KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
factory.register(new ProviderEventListener() {
|
factory.register(new ProviderEventListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -202,38 +194,38 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||||
|
|
||||||
cluster.registerListener(REALM_REMOVED_SESSION_EVENT, new AbstractUserSessionClusterListener<RealmRemovedSessionEvent>(sessionFactory) {
|
cluster.registerListener(REALM_REMOVED_SESSION_EVENT,
|
||||||
|
new AbstractUserSessionClusterListener<RealmRemovedSessionEvent, UserSessionProvider>(sessionFactory, UserSessionProvider.class) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void eventReceived(KeycloakSession session, InfinispanUserSessionProvider provider, RealmRemovedSessionEvent sessionEvent) {
|
protected void eventReceived(KeycloakSession session, UserSessionProvider provider, RealmRemovedSessionEvent sessionEvent) {
|
||||||
provider.onRealmRemovedEvent(sessionEvent.getRealmId());
|
if (provider instanceof InfinispanUserSessionProvider) {
|
||||||
|
((InfinispanUserSessionProvider) provider).onRealmRemovedEvent(sessionEvent.getRealmId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.registerListener(CLIENT_REMOVED_SESSION_EVENT, new AbstractUserSessionClusterListener<ClientRemovedSessionEvent>(sessionFactory) {
|
cluster.registerListener(CLIENT_REMOVED_SESSION_EVENT,
|
||||||
|
new AbstractUserSessionClusterListener<ClientRemovedSessionEvent, UserSessionProvider>(sessionFactory, UserSessionProvider.class) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void eventReceived(KeycloakSession session, InfinispanUserSessionProvider provider, ClientRemovedSessionEvent sessionEvent) {
|
protected void eventReceived(KeycloakSession session, UserSessionProvider provider, ClientRemovedSessionEvent sessionEvent) {
|
||||||
provider.onClientRemovedEvent(sessionEvent.getRealmId(), sessionEvent.getClientUuid());
|
if (provider instanceof InfinispanUserSessionProvider) {
|
||||||
|
((InfinispanUserSessionProvider) provider).onClientRemovedEvent(sessionEvent.getRealmId(), sessionEvent.getClientUuid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.registerListener(REMOVE_USER_SESSIONS_EVENT, new AbstractUserSessionClusterListener<RemoveUserSessionsEvent>(sessionFactory) {
|
cluster.registerListener(REMOVE_USER_SESSIONS_EVENT,
|
||||||
|
new AbstractUserSessionClusterListener<RemoveUserSessionsEvent, UserSessionProvider>(sessionFactory, UserSessionProvider.class) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void eventReceived(KeycloakSession session, InfinispanUserSessionProvider provider, RemoveUserSessionsEvent sessionEvent) {
|
protected void eventReceived(KeycloakSession session, UserSessionProvider provider, RemoveUserSessionsEvent sessionEvent) {
|
||||||
provider.onRemoveUserSessionsEvent(sessionEvent.getRealmId());
|
if (provider instanceof InfinispanUserSessionProvider) {
|
||||||
}
|
((InfinispanUserSessionProvider) provider).onRemoveUserSessionsEvent(sessionEvent.getRealmId());
|
||||||
|
}
|
||||||
});
|
|
||||||
|
|
||||||
cluster.registerListener(REMOVE_ALL_LOGIN_FAILURES_EVENT, new AbstractUserSessionClusterListener<RemoveAllUserLoginFailuresEvent>(sessionFactory) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void eventReceived(KeycloakSession session, InfinispanUserSessionProvider provider, RemoveAllUserLoginFailuresEvent sessionEvent) {
|
|
||||||
provider.onRemoveAllUserLoginFailuresEvent(sessionEvent.getRealmId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -276,11 +268,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
checkRemoteCache(session, offlineClientSessionsCache, (RealmModel realm) -> {
|
checkRemoteCache(session, offlineClientSessionsCache, (RealmModel realm) -> {
|
||||||
return Time.toMillis(realm.getOfflineSessionIdleTimeout());
|
return Time.toMillis(realm.getOfflineSessionIdleTimeout());
|
||||||
}, SessionTimeouts::getOfflineClientSessionLifespanMs, SessionTimeouts::getOfflineClientSessionMaxIdleMs);
|
}, SessionTimeouts::getOfflineClientSessionLifespanMs, SessionTimeouts::getOfflineClientSessionMaxIdleMs);
|
||||||
|
|
||||||
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailuresCache = ispn.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
|
||||||
checkRemoteCache(session, loginFailuresCache, (RealmModel realm) -> {
|
|
||||||
return Time.toMillis(realm.getMaxDeltaTimeSeconds());
|
|
||||||
}, SessionTimeouts::getLoginFailuresLifespanMs, SessionTimeouts::getLoginFailuresMaxIdleMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <K, V extends SessionEntity> RemoteCache checkRemoteCache(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> ispnCache, RemoteCacheInvoker.MaxIdleTimeLoader maxIdleLoader,
|
private <K, V extends SessionEntity> RemoteCache checkRemoteCache(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> ispnCache, RemoteCacheInvoker.MaxIdleTimeLoader maxIdleLoader,
|
||||||
|
|
|
@ -27,11 +27,11 @@ import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
*/
|
*/
|
||||||
public class UserLoginFailureAdapter implements UserLoginFailureModel {
|
public class UserLoginFailureAdapter implements UserLoginFailureModel {
|
||||||
|
|
||||||
private InfinispanUserSessionProvider provider;
|
private InfinispanUserLoginFailureProvider provider;
|
||||||
private LoginFailureKey key;
|
private LoginFailureKey key;
|
||||||
private LoginFailureEntity entity;
|
private LoginFailureEntity entity;
|
||||||
|
|
||||||
public UserLoginFailureAdapter(InfinispanUserSessionProvider provider, LoginFailureKey key, LoginFailureEntity entity) {
|
public UserLoginFailureAdapter(InfinispanUserLoginFailureProvider provider, LoginFailureKey key, LoginFailureEntity entity) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
|
|
|
@ -24,30 +24,31 @@ import org.keycloak.cluster.ClusterProvider;
|
||||||
import org.keycloak.connections.infinispan.TopologyInfo;
|
import org.keycloak.connections.infinispan.TopologyInfo;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.UserSessionProvider;
|
|
||||||
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProvider;
|
|
||||||
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
|
||||||
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractUserSessionClusterListener<SE extends SessionClusterEvent> implements ClusterListener {
|
public abstract class AbstractUserSessionClusterListener<SE extends SessionClusterEvent, T extends Provider> implements ClusterListener {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(AbstractUserSessionClusterListener.class);
|
private static final Logger log = Logger.getLogger(AbstractUserSessionClusterListener.class);
|
||||||
|
|
||||||
private final KeycloakSessionFactory sessionFactory;
|
private final KeycloakSessionFactory sessionFactory;
|
||||||
|
|
||||||
public AbstractUserSessionClusterListener(KeycloakSessionFactory sessionFactory) {
|
private final Class<T> providerClazz;
|
||||||
|
|
||||||
|
public AbstractUserSessionClusterListener(KeycloakSessionFactory sessionFactory, Class<T> providerClazz) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
|
this.providerClazz = providerClazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventReceived(ClusterEvent event) {
|
public void eventReceived(ClusterEvent event) {
|
||||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, (KeycloakSession session) -> {
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, (KeycloakSession session) -> {
|
||||||
InfinispanUserSessionProvider provider = (InfinispanUserSessionProvider) session.getProvider(UserSessionProvider.class, InfinispanUserSessionProviderFactory.PROVIDER_ID);
|
T provider = session.getProvider(providerClazz);
|
||||||
SE sessionEvent = (SE) event;
|
SE sessionEvent = (SE) event;
|
||||||
|
|
||||||
boolean shouldResendEvent = shouldResendEvent(session, sessionEvent);
|
boolean shouldResendEvent = shouldResendEvent(session, sessionEvent);
|
||||||
|
@ -65,7 +66,7 @@ public abstract class AbstractUserSessionClusterListener<SE extends SessionClust
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void eventReceived(KeycloakSession session, InfinispanUserSessionProvider provider, SE sessionEvent);
|
protected abstract void eventReceived(KeycloakSession session, T provider, SE sessionEvent);
|
||||||
|
|
||||||
|
|
||||||
private boolean shouldResendEvent(KeycloakSession session, SessionClusterEvent event) {
|
private boolean shouldResendEvent(KeycloakSession session, SessionClusterEvent event) {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2021 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.loginFailure;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractUserLoginFailureEntity<K> implements AbstractEntity<K> {
|
||||||
|
private K id;
|
||||||
|
private String realmId;
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag signalizing that any of the setters has been meaningfully used.
|
||||||
|
*/
|
||||||
|
protected boolean updated;
|
||||||
|
|
||||||
|
private int failedLoginNotBefore;
|
||||||
|
private int numFailures;
|
||||||
|
private long lastFailure;
|
||||||
|
private String lastIPFailure;
|
||||||
|
|
||||||
|
public AbstractUserLoginFailureEntity() {
|
||||||
|
this.id = null;
|
||||||
|
this.realmId = null;
|
||||||
|
this.userId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractUserLoginFailureEntity(K id, String realmId, String userId) {
|
||||||
|
this.id = id;
|
||||||
|
this.realmId = realmId;
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdated() {
|
||||||
|
return this.updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.updated |= !Objects.equals(this.realmId, realmId);
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.updated |= !Objects.equals(this.userId, userId);
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFailedLoginNotBefore() {
|
||||||
|
return failedLoginNotBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailedLoginNotBefore(int failedLoginNotBefore) {
|
||||||
|
this.updated |= this.failedLoginNotBefore != failedLoginNotBefore;
|
||||||
|
this.failedLoginNotBefore = failedLoginNotBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumFailures() {
|
||||||
|
return numFailures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumFailures(int numFailures) {
|
||||||
|
this.updated |= this.numFailures != numFailures;
|
||||||
|
this.numFailures = numFailures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastFailure() {
|
||||||
|
return lastFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastFailure(long lastFailure) {
|
||||||
|
this.updated |= this.lastFailure != lastFailure;
|
||||||
|
this.lastFailure = lastFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastIPFailure() {
|
||||||
|
return lastIPFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastIPFailure(String lastIPFailure) {
|
||||||
|
this.updated |= !Objects.equals(this.lastIPFailure, lastIPFailure);
|
||||||
|
this.lastIPFailure = lastIPFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearFailures() {
|
||||||
|
this.updated |= this.failedLoginNotBefore != 0 || this.numFailures != 0 ||
|
||||||
|
this.lastFailure != 0l || this.lastIPFailure != null;
|
||||||
|
this.failedLoginNotBefore = this.numFailures = 0;
|
||||||
|
this.lastFailure = 0l;
|
||||||
|
this.lastIPFailure = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s@%08x", getId(), hashCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.loginFailure;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserLoginFailureModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractUserLoginFailureModel <E extends AbstractEntity> implements UserLoginFailureModel {
|
||||||
|
protected final KeycloakSession session;
|
||||||
|
protected final RealmModel realm;
|
||||||
|
protected final E entity;
|
||||||
|
|
||||||
|
public AbstractUserLoginFailureModel(KeycloakSession session, RealmModel realm, E entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
Objects.requireNonNull(realm, "realm");
|
||||||
|
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof UserLoginFailureModel)) return false;
|
||||||
|
|
||||||
|
MapUserLoginFailureAdapter that = (MapUserLoginFailureAdapter) o;
|
||||||
|
return Objects.equals(that.entity.getId(), entity.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return entity.getId().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.loginFailure;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class MapUserLoginFailureAdapter extends AbstractUserLoginFailureModel<MapUserLoginFailureEntity> {
|
||||||
|
public MapUserLoginFailureAdapter(KeycloakSession session, RealmModel realm, MapUserLoginFailureEntity entity) {
|
||||||
|
super(session, realm, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUserId() {
|
||||||
|
return entity.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFailedLoginNotBefore() {
|
||||||
|
return entity.getFailedLoginNotBefore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFailedLoginNotBefore(int notBefore) {
|
||||||
|
entity.setFailedLoginNotBefore(notBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumFailures() {
|
||||||
|
return entity.getNumFailures();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void incrementFailures() {
|
||||||
|
entity.setNumFailures(getNumFailures() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearFailures() {
|
||||||
|
entity.clearFailures();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLastFailure() {
|
||||||
|
return entity.getLastFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastFailure(long lastFailure) {
|
||||||
|
entity.setLastFailure(lastFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLastIPFailure() {
|
||||||
|
return entity.getLastIPFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastIPFailure(String ip) {
|
||||||
|
entity.setLastIPFailure(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s@%08x", entity.getId(), hashCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.loginFailure;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class MapUserLoginFailureEntity extends AbstractUserLoginFailureEntity<UUID> {
|
||||||
|
protected MapUserLoginFailureEntity() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapUserLoginFailureEntity(UUID id, String realmId, String userId) {
|
||||||
|
super(id, realmId, userId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.loginFailure;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.UserLoginFailureProvider;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserLoginFailureModel;
|
||||||
|
import org.keycloak.models.map.common.Serialization;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(MapUserLoginFailureProvider.class);
|
||||||
|
private final KeycloakSession session;
|
||||||
|
protected final MapKeycloakTransaction<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureTx;
|
||||||
|
private final MapStorage<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureStore;
|
||||||
|
|
||||||
|
public MapUserLoginFailureProvider(KeycloakSession session, MapStorage<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureStore) {
|
||||||
|
this.session = session;
|
||||||
|
this.userLoginFailureStore = userLoginFailureStore;
|
||||||
|
|
||||||
|
userLoginFailureTx = userLoginFailureStore.createTransaction(session);
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(userLoginFailureTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureEntityToAdapterFunc(RealmModel realm) {
|
||||||
|
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
|
||||||
|
return origEntity -> new MapUserLoginFailureAdapter(session, realm, registerEntityForChanges(origEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapUserLoginFailureEntity registerEntityForChanges(MapUserLoginFailureEntity origEntity) {
|
||||||
|
MapUserLoginFailureEntity res = userLoginFailureTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
|
||||||
|
userLoginFailureTx.updateIfChanged(origEntity.getId(), res, MapUserLoginFailureEntity::isUpdated);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
ModelCriteriaBuilder<UserLoginFailureModel> mcb = userLoginFailureStore.createCriteriaBuilder()
|
||||||
|
.compare(UserLoginFailureModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())
|
||||||
|
.compare(UserLoginFailureModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, userId);
|
||||||
|
|
||||||
|
LOG.tracef("getUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
|
||||||
|
|
||||||
|
return userLoginFailureTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.findFirst()
|
||||||
|
.map(userLoginFailureEntityToAdapterFunc(realm))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
ModelCriteriaBuilder<UserLoginFailureModel> mcb = userLoginFailureStore.createCriteriaBuilder()
|
||||||
|
.compare(UserLoginFailureModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())
|
||||||
|
.compare(UserLoginFailureModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, userId);
|
||||||
|
|
||||||
|
LOG.tracef("addUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
|
||||||
|
|
||||||
|
MapUserLoginFailureEntity userLoginFailureEntity = userLoginFailureTx.getUpdatedNotRemoved(mcb).findFirst().orElse(null);
|
||||||
|
|
||||||
|
if (userLoginFailureEntity == null) {
|
||||||
|
userLoginFailureEntity = new MapUserLoginFailureEntity(UUID.randomUUID(), realm.getId(), userId);
|
||||||
|
|
||||||
|
userLoginFailureTx.create(userLoginFailureEntity.getId(), userLoginFailureEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userLoginFailureEntityToAdapterFunc(realm).apply(userLoginFailureEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
ModelCriteriaBuilder<UserLoginFailureModel> mcb = userLoginFailureStore.createCriteriaBuilder()
|
||||||
|
.compare(UserLoginFailureModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())
|
||||||
|
.compare(UserLoginFailureModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, userId);
|
||||||
|
|
||||||
|
LOG.tracef("removeUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
|
||||||
|
|
||||||
|
userLoginFailureTx.delete(UUID.randomUUID(), mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAllUserLoginFailures(RealmModel realm) {
|
||||||
|
ModelCriteriaBuilder<UserLoginFailureModel> mcb = userLoginFailureStore.createCriteriaBuilder()
|
||||||
|
.compare(UserLoginFailureModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId());
|
||||||
|
|
||||||
|
LOG.tracef("removeAllUserLoginFailures(%s)%s", realm, getShortStackTrace());
|
||||||
|
|
||||||
|
userLoginFailureTx.delete(UUID.randomUUID(), mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.loginFailure;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserLoginFailureProvider;
|
||||||
|
import org.keycloak.models.UserLoginFailureProviderFactory;
|
||||||
|
import org.keycloak.models.UserLoginFailureModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class MapUserLoginFailureProviderFactory extends AbstractMapProviderFactory<UserLoginFailureProvider>
|
||||||
|
implements UserLoginFailureProviderFactory {
|
||||||
|
|
||||||
|
private MapStorage<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureStore;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
||||||
|
userLoginFailureStore = sp.getStorage("userLoginFailures", UUID.class, MapUserLoginFailureEntity.class, UserLoginFailureModel.class);
|
||||||
|
|
||||||
|
factory.register(event -> {
|
||||||
|
if (event instanceof UserModel.UserRemovedEvent) {
|
||||||
|
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
|
||||||
|
|
||||||
|
MapUserLoginFailureProvider provider = MapUserLoginFailureProviderFactory.this.create(userRemovedEvent.getKeycloakSession());
|
||||||
|
provider.removeUserLoginFailure(userRemovedEvent.getRealm(), userRemovedEvent.getUser().getId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
factory.register(event -> {
|
||||||
|
if (event instanceof RealmModel.RealmRemovedEvent) {
|
||||||
|
RealmModel.RealmRemovedEvent realmRemovedEvent = (RealmModel.RealmRemovedEvent) event;
|
||||||
|
|
||||||
|
MapUserLoginFailureProvider provider = MapUserLoginFailureProviderFactory.this.create(realmRemovedEvent.getKeycloakSession());
|
||||||
|
provider.removeAllUserLoginFailures(realmRemovedEvent.getRealm());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapUserLoginFailureProvider create(KeycloakSession session) {
|
||||||
|
return new MapUserLoginFailureProvider(session, userLoginFailureStore);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1547,4 +1547,9 @@ public class MapRealmAdapter extends AbstractRealmModel<MapRealmEntity> implemen
|
||||||
public OAuth2DeviceConfig getOAuth2DeviceConfig() {
|
public OAuth2DeviceConfig getOAuth2DeviceConfig() {
|
||||||
return new OAuth2DeviceConfig(this);
|
return new OAuth2DeviceConfig(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s@%08x", getId(), hashCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,15 @@ import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.authorization.model.Scope;
|
import org.keycloak.authorization.model.Scope;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserLoginFailureModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.map.authSession.AbstractRootAuthenticationSessionEntity;
|
import org.keycloak.models.map.authSession.AbstractRootAuthenticationSessionEntity;
|
||||||
import org.keycloak.models.map.authorization.entity.AbstractPermissionTicketEntity;
|
import org.keycloak.models.map.authorization.entity.AbstractPermissionTicketEntity;
|
||||||
import org.keycloak.models.map.authorization.entity.AbstractPolicyEntity;
|
import org.keycloak.models.map.authorization.entity.AbstractPolicyEntity;
|
||||||
|
@ -39,6 +42,9 @@ import org.keycloak.models.map.common.AbstractEntity;
|
||||||
import org.keycloak.models.map.group.AbstractGroupEntity;
|
import org.keycloak.models.map.group.AbstractGroupEntity;
|
||||||
import org.keycloak.models.map.realm.AbstractRealmEntity;
|
import org.keycloak.models.map.realm.AbstractRealmEntity;
|
||||||
import org.keycloak.models.map.role.AbstractRoleEntity;
|
import org.keycloak.models.map.role.AbstractRoleEntity;
|
||||||
|
import org.keycloak.models.map.userSession.AbstractAuthenticatedClientSessionEntity;
|
||||||
|
import org.keycloak.models.map.loginFailure.AbstractUserLoginFailureEntity;
|
||||||
|
import org.keycloak.models.map.userSession.AbstractUserSessionEntity;
|
||||||
import org.keycloak.storage.SearchableModelField;
|
import org.keycloak.storage.SearchableModelField;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -55,6 +61,8 @@ import java.util.Objects;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
|
@ -73,6 +81,9 @@ public class MapFieldPredicates {
|
||||||
public static final Map<SearchableModelField<Scope>, UpdatePredicatesFunc<Object, AbstractScopeEntity<Object>, Scope>> AUTHZ_SCOPE_PREDICATES = basePredicates(Scope.SearchableFields.ID);
|
public static final Map<SearchableModelField<Scope>, UpdatePredicatesFunc<Object, AbstractScopeEntity<Object>, Scope>> AUTHZ_SCOPE_PREDICATES = basePredicates(Scope.SearchableFields.ID);
|
||||||
public static final Map<SearchableModelField<PermissionTicket>, UpdatePredicatesFunc<Object, AbstractPermissionTicketEntity<Object>, PermissionTicket>> AUTHZ_PERMISSION_TICKET_PREDICATES = basePredicates(PermissionTicket.SearchableFields.ID);
|
public static final Map<SearchableModelField<PermissionTicket>, UpdatePredicatesFunc<Object, AbstractPermissionTicketEntity<Object>, PermissionTicket>> AUTHZ_PERMISSION_TICKET_PREDICATES = basePredicates(PermissionTicket.SearchableFields.ID);
|
||||||
public static final Map<SearchableModelField<Policy>, UpdatePredicatesFunc<Object, AbstractPolicyEntity<Object>, Policy>> AUTHZ_POLICY_PREDICATES = basePredicates(Policy.SearchableFields.ID);
|
public static final Map<SearchableModelField<Policy>, UpdatePredicatesFunc<Object, AbstractPolicyEntity<Object>, Policy>> AUTHZ_POLICY_PREDICATES = basePredicates(Policy.SearchableFields.ID);
|
||||||
|
public static final Map<SearchableModelField<UserSessionModel>, UpdatePredicatesFunc<Object, AbstractUserSessionEntity<Object>, UserSessionModel>> USER_SESSION_PREDICATES = basePredicates(UserSessionModel.SearchableFields.ID);
|
||||||
|
public static final Map<SearchableModelField<AuthenticatedClientSessionModel>, UpdatePredicatesFunc<Object, AbstractAuthenticatedClientSessionEntity<Object>, AuthenticatedClientSessionModel>> CLIENT_SESSION_PREDICATES = basePredicates(AuthenticatedClientSessionModel.SearchableFields.ID);
|
||||||
|
public static final Map<SearchableModelField<UserLoginFailureModel>, UpdatePredicatesFunc<Object, AbstractUserLoginFailureEntity<Object>, UserLoginFailureModel>> USER_LOGIN_FAILURE_PREDICATES = basePredicates(UserLoginFailureModel.SearchableFields.ID);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static final Map<Class<?>, Map> PREDICATES = new HashMap<>();
|
private static final Map<Class<?>, Map> PREDICATES = new HashMap<>();
|
||||||
|
@ -154,6 +165,24 @@ public class MapFieldPredicates {
|
||||||
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.SCOPE_ID, MapFieldPredicates::checkPolicyScopes);
|
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.SCOPE_ID, MapFieldPredicates::checkPolicyScopes);
|
||||||
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.CONFIG, MapFieldPredicates::checkPolicyConfig);
|
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.CONFIG, MapFieldPredicates::checkPolicyConfig);
|
||||||
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.ASSOCIATED_POLICY_ID, MapFieldPredicates::checkAssociatedPolicy);
|
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.ASSOCIATED_POLICY_ID, MapFieldPredicates::checkAssociatedPolicy);
|
||||||
|
|
||||||
|
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, use -> use.getNote(CORRESPONDING_SESSION_ID));
|
||||||
|
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.REALM_ID, AbstractUserSessionEntity::getRealmId);
|
||||||
|
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.USER_ID, AbstractUserSessionEntity::getUserId);
|
||||||
|
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.CLIENT_ID, MapFieldPredicates::checkUserSessionContainsAuthenticatedClientSession);
|
||||||
|
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.BROKER_SESSION_ID, AbstractUserSessionEntity::getBrokerSessionId);
|
||||||
|
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.BROKER_USER_ID, AbstractUserSessionEntity::getBrokerUserId);
|
||||||
|
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.IS_OFFLINE, AbstractUserSessionEntity::isOffline);
|
||||||
|
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.LAST_SESSION_REFRESH, AbstractUserSessionEntity::getLastSessionRefresh);
|
||||||
|
|
||||||
|
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.REALM_ID, AbstractAuthenticatedClientSessionEntity::getRealmId);
|
||||||
|
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.CLIENT_ID, AbstractAuthenticatedClientSessionEntity::getClientId);
|
||||||
|
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, AbstractAuthenticatedClientSessionEntity::getUserSessionId);
|
||||||
|
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.IS_OFFLINE, AbstractAuthenticatedClientSessionEntity::isOffline);
|
||||||
|
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.TIMESTAMP, AbstractAuthenticatedClientSessionEntity::getTimestamp);
|
||||||
|
|
||||||
|
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.REALM_ID, AbstractUserLoginFailureEntity::getRealmId);
|
||||||
|
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.USER_ID, AbstractUserLoginFailureEntity::getUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -169,6 +198,9 @@ public class MapFieldPredicates {
|
||||||
PREDICATES.put(Scope.class, AUTHZ_SCOPE_PREDICATES);
|
PREDICATES.put(Scope.class, AUTHZ_SCOPE_PREDICATES);
|
||||||
PREDICATES.put(PermissionTicket.class, AUTHZ_PERMISSION_TICKET_PREDICATES);
|
PREDICATES.put(PermissionTicket.class, AUTHZ_PERMISSION_TICKET_PREDICATES);
|
||||||
PREDICATES.put(Policy.class, AUTHZ_POLICY_PREDICATES);
|
PREDICATES.put(Policy.class, AUTHZ_POLICY_PREDICATES);
|
||||||
|
PREDICATES.put(UserSessionModel.class, USER_SESSION_PREDICATES);
|
||||||
|
PREDICATES.put(AuthenticatedClientSessionModel.class, CLIENT_SESSION_PREDICATES);
|
||||||
|
PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <K, V extends AbstractEntity<K>, M> void put(
|
private static <K, V extends AbstractEntity<K>, M> void put(
|
||||||
|
@ -423,6 +455,13 @@ public class MapFieldPredicates {
|
||||||
private static MapModelCriteriaBuilder<Object, AbstractRealmEntity<Object>, RealmModel> checkRealmsWithComponentType(MapModelCriteriaBuilder<Object, AbstractRealmEntity<Object>, RealmModel> mcb, Operator op, Object[] values) {
|
private static MapModelCriteriaBuilder<Object, AbstractRealmEntity<Object>, RealmModel> checkRealmsWithComponentType(MapModelCriteriaBuilder<Object, AbstractRealmEntity<Object>, RealmModel> mcb, Operator op, Object[] values) {
|
||||||
String providerType = ensureEqSingleValue(RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, "component_provider_type", op, values);
|
String providerType = ensureEqSingleValue(RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, "component_provider_type", op, values);
|
||||||
Function<AbstractRealmEntity<Object>, ?> getter = realmEntity -> realmEntity.getComponents().anyMatch(component -> component.getProviderType().equals(providerType));
|
Function<AbstractRealmEntity<Object>, ?> getter = realmEntity -> realmEntity.getComponents().anyMatch(component -> component.getProviderType().equals(providerType));
|
||||||
|
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MapModelCriteriaBuilder<Object, AbstractUserSessionEntity<Object>, UserSessionModel> checkUserSessionContainsAuthenticatedClientSession(MapModelCriteriaBuilder<Object, AbstractUserSessionEntity<Object>, UserSessionModel> mcb, Operator op, Object[] values) {
|
||||||
|
String clientId = ensureEqSingleValue(UserSessionModel.SearchableFields.CLIENT_ID, "client_id", op, values);
|
||||||
|
Function<AbstractUserSessionEntity<Object>, ?> getter;
|
||||||
|
getter = use -> (use.getAuthenticatedClientSessions().containsKey(clientId));
|
||||||
|
|
||||||
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
|
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -388,7 +388,7 @@ public class MapKeycloakTransaction<K, V extends AbstractEntity<K>, M> implement
|
||||||
|
|
||||||
Predicate<? super V> entityFilter = mmcb.getEntityFilter();
|
Predicate<? super V> entityFilter = mmcb.getEntityFilter();
|
||||||
Predicate<? super K> keyFilter = ((MapModelCriteriaBuilder) mcb).getKeyFilter();
|
Predicate<? super K> keyFilter = ((MapModelCriteriaBuilder) mcb).getKeyFilter();
|
||||||
return v -> v != null && ! (keyFilter.test(v.getId()) && entityFilter.test(v));
|
return v -> v == null || ! (keyFilter.test(v.getId()) && entityFilter.test(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
package org.keycloak.models.map.storage.chm;
|
package org.keycloak.models.map.storage.chm;
|
||||||
|
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.map.common.AbstractEntity;
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
import org.keycloak.models.map.common.Serialization;
|
import org.keycloak.models.map.common.Serialization;
|
||||||
import com.fasterxml.jackson.databind.JavaType;
|
import com.fasterxml.jackson.databind.JavaType;
|
||||||
|
@ -31,6 +33,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -92,7 +96,14 @@ public class ConcurrentHashMapStorageProvider implements MapStorageProvider {
|
||||||
|
|
||||||
private <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> loadMap(String fileName,
|
private <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> loadMap(String fileName,
|
||||||
Class<V> valueType, Class<M> modelType, EnumSet<Flag> flags) {
|
Class<V> valueType, Class<M> modelType, EnumSet<Flag> flags) {
|
||||||
ConcurrentHashMapStorage<K, V, M> store = new ConcurrentHashMapStorage<>(modelType);
|
ConcurrentHashMapStorage<K, V, M> store;
|
||||||
|
if (modelType == UserSessionModel.class) {
|
||||||
|
ConcurrentHashMapStorage clientSessionStore =
|
||||||
|
getStorage("clientSessions", UUID.class, MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
|
||||||
|
store = new UserSessionConcurrentHashMapStorage<>(clientSessionStore);
|
||||||
|
} else {
|
||||||
|
store = new ConcurrentHashMapStorage<>(modelType);
|
||||||
|
}
|
||||||
|
|
||||||
if (! flags.contains(Flag.INITIALIZE_EMPTY)) {
|
if (! flags.contains(Flag.INITIALIZE_EMPTY)) {
|
||||||
final File f = getFile(fileName);
|
final File f = getFile(fileName);
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 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.map.storage.chm;
|
||||||
|
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
|
||||||
|
import org.keycloak.models.map.userSession.AbstractAuthenticatedClientSessionEntity;
|
||||||
|
import org.keycloak.models.map.userSession.AbstractUserSessionEntity;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User session storage with a naive implementation of referential integrity in client to user session relation, restricted to
|
||||||
|
* ON DELETE CASCADE functionality.
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class UserSessionConcurrentHashMapStorage<K> extends ConcurrentHashMapStorage<K, AbstractUserSessionEntity<K>, UserSessionModel> {
|
||||||
|
|
||||||
|
private final ConcurrentHashMapStorage<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionStore;
|
||||||
|
|
||||||
|
private class Transaction extends MapKeycloakTransaction<K, AbstractUserSessionEntity<K>, UserSessionModel> {
|
||||||
|
|
||||||
|
private final MapKeycloakTransaction<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionTr;
|
||||||
|
|
||||||
|
public Transaction(MapKeycloakTransaction<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionTr) {
|
||||||
|
super(UserSessionConcurrentHashMapStorage.this);
|
||||||
|
this.clientSessionTr = clientSessionTr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long delete(K artificialKey, ModelCriteriaBuilder<UserSessionModel> mcb) {
|
||||||
|
Set<K> ids = getUpdatedNotRemoved(mcb).map(AbstractEntity::getId).collect(Collectors.toSet());
|
||||||
|
ModelCriteriaBuilder<AuthenticatedClientSessionModel> csMcb = clientSessionStore.createCriteriaBuilder().compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, Operator.IN, ids);
|
||||||
|
clientSessionTr.delete(artificialKey, csMcb);
|
||||||
|
return super.delete(artificialKey, mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(K key) {
|
||||||
|
ModelCriteriaBuilder<AuthenticatedClientSessionModel> csMcb = clientSessionStore.createCriteriaBuilder().compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, Operator.EQ, key);
|
||||||
|
clientSessionTr.delete(key, csMcb);
|
||||||
|
super.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public UserSessionConcurrentHashMapStorage(ConcurrentHashMapStorage<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionStore) {
|
||||||
|
super(UserSessionModel.class);
|
||||||
|
this.clientSessionStore = clientSessionStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public MapKeycloakTransaction<K, AbstractUserSessionEntity<K>, UserSessionModel> createTransaction(KeycloakSession session) {
|
||||||
|
MapKeycloakTransaction sessionTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
|
||||||
|
return sessionTransaction == null ? new Transaction(clientSessionStore.createTransaction(session)) : (MapKeycloakTransaction<K, AbstractUserSessionEntity<K>, UserSessionModel>) sessionTransaction;
|
||||||
|
}
|
||||||
|
}
|
|
@ -304,4 +304,9 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
|
||||||
public void deleteRoleMapping(RoleModel role) {
|
public void deleteRoleMapping(RoleModel role) {
|
||||||
entity.removeRolesMembership(role.getId());
|
entity.removeRolesMembership(role.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s@%08x", getId(), hashCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractAuthenticatedClientSessionEntity<K> implements AbstractEntity<K> {
|
||||||
|
|
||||||
|
private K id;
|
||||||
|
private String userSessionId;
|
||||||
|
private String realmId;
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag signalizing that any of the setters has been meaningfully used.
|
||||||
|
*/
|
||||||
|
protected boolean updated;
|
||||||
|
|
||||||
|
private String authMethod;
|
||||||
|
private String redirectUri;
|
||||||
|
private volatile int timestamp;
|
||||||
|
private long expiration;
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
private Map<String, String> notes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private String currentRefreshToken;
|
||||||
|
private int currentRefreshTokenUseCount;
|
||||||
|
|
||||||
|
private boolean offline;
|
||||||
|
|
||||||
|
public AbstractAuthenticatedClientSessionEntity() {
|
||||||
|
this.id = null;
|
||||||
|
this.realmId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractAuthenticatedClientSessionEntity(K id, String userSessionId, String realmId, String clientId, boolean offline) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
Objects.requireNonNull(userSessionId, "userSessionId");
|
||||||
|
Objects.requireNonNull(realmId, "realmId");
|
||||||
|
Objects.requireNonNull(clientId, "clientId");
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.userSessionId = userSessionId;
|
||||||
|
this.realmId = realmId;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.offline = offline;
|
||||||
|
this.timestamp = Time.currentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdated() {
|
||||||
|
return this.updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.updated |= !Objects.equals(this.realmId, realmId);
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.updated |= !Objects.equals(this.clientId, clientId);
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserSessionId() {
|
||||||
|
return userSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserSessionId(String userSessionId) {
|
||||||
|
this.updated |= !Objects.equals(this.userSessionId, userSessionId);
|
||||||
|
this.userSessionId = userSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthMethod() {
|
||||||
|
return authMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthMethod(String authMethod) {
|
||||||
|
this.updated |= !Objects.equals(this.authMethod, authMethod);
|
||||||
|
this.authMethod = authMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRedirectUri() {
|
||||||
|
return redirectUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRedirectUri(String redirectUri) {
|
||||||
|
this.updated |= !Objects.equals(this.redirectUri, redirectUri);
|
||||||
|
this.redirectUri = redirectUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(int timestamp) {
|
||||||
|
this.updated |= this.timestamp != timestamp;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExpiration() {
|
||||||
|
return expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiration(long expiration) {
|
||||||
|
this.updated |= this.expiration != expiration;
|
||||||
|
this.expiration = expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(String action) {
|
||||||
|
this.updated |= !Objects.equals(this.action, action);
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getNotes() {
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotes(Map<String, String> notes) {
|
||||||
|
this.updated |= !Objects.equals(this.notes, notes);
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String removeNote(String name) {
|
||||||
|
String note = this.notes.remove(name);
|
||||||
|
this.updated |= note != null;
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNote(String name, String value) {
|
||||||
|
this.updated |= !Objects.equals(this.notes.put(name, value), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentRefreshToken() {
|
||||||
|
return currentRefreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentRefreshToken(String currentRefreshToken) {
|
||||||
|
this.updated |= !Objects.equals(this.currentRefreshToken, currentRefreshToken);
|
||||||
|
this.currentRefreshToken = currentRefreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentRefreshTokenUseCount() {
|
||||||
|
return currentRefreshTokenUseCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) {
|
||||||
|
this.updated |= this.currentRefreshTokenUseCount != currentRefreshTokenUseCount;
|
||||||
|
this.currentRefreshTokenUseCount = currentRefreshTokenUseCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOffline() {
|
||||||
|
return offline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOffline(boolean offline) {
|
||||||
|
this.updated |= this.offline != offline;
|
||||||
|
this.offline = offline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s@%08x", getId(), hashCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractAuthenticatedClientSessionModel<E extends AbstractEntity> implements AuthenticatedClientSessionModel {
|
||||||
|
protected final KeycloakSession session;
|
||||||
|
protected final RealmModel realm;
|
||||||
|
protected ClientModel client;
|
||||||
|
protected UserSessionModel userSession;
|
||||||
|
protected final E entity;
|
||||||
|
|
||||||
|
public AbstractAuthenticatedClientSessionModel(KeycloakSession session, RealmModel realm, ClientModel client,
|
||||||
|
UserSessionModel userSession, E entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
Objects.requireNonNull(realm, "realm");
|
||||||
|
Objects.requireNonNull(client, "client");
|
||||||
|
Objects.requireNonNull(userSession, "userSession");
|
||||||
|
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.client = client;
|
||||||
|
this.userSession = userSession;
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof AuthenticatedClientSessionModel)) return false;
|
||||||
|
|
||||||
|
AuthenticatedClientSessionModel that = (AuthenticatedClientSessionModel) o;
|
||||||
|
return Objects.equals(that.getId(), getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getId().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,288 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractUserSessionEntity<K> implements AbstractEntity<K> {
|
||||||
|
private K id;
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag signalizing that any of the setters has been meaningfully used.
|
||||||
|
*/
|
||||||
|
protected boolean updated;
|
||||||
|
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
private String brokerSessionId;
|
||||||
|
private String brokerUserId;
|
||||||
|
|
||||||
|
private String loginUsername;
|
||||||
|
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
private String authMethod;
|
||||||
|
|
||||||
|
private boolean rememberMe;
|
||||||
|
|
||||||
|
private int started;
|
||||||
|
|
||||||
|
private int lastSessionRefresh;
|
||||||
|
|
||||||
|
private long expiration;
|
||||||
|
|
||||||
|
private Map<String, String> notes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private UserSessionModel.State state;
|
||||||
|
|
||||||
|
private UserSessionModel.SessionPersistenceState persistenceState = UserSessionModel.SessionPersistenceState.PERSISTENT;
|
||||||
|
|
||||||
|
private Map<String, K> authenticatedClientSessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private boolean offline;
|
||||||
|
|
||||||
|
public AbstractUserSessionEntity() {
|
||||||
|
this.id = null;
|
||||||
|
this.realmId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractUserSessionEntity(K id, String realmId) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
Objects.requireNonNull(realmId, "realmId");
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractUserSessionEntity(K id, RealmModel realm, UserModel user, String loginUsername, String ipAddress,
|
||||||
|
String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId,
|
||||||
|
boolean offline) {
|
||||||
|
this.id = id;
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
this.userId = user.getId();
|
||||||
|
this.loginUsername = loginUsername;
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
this.authMethod = authMethod;
|
||||||
|
this.rememberMe = rememberMe;
|
||||||
|
this.brokerSessionId = brokerSessionId;
|
||||||
|
this.brokerUserId = brokerUserId;
|
||||||
|
this.started = Time.currentTime();
|
||||||
|
this.lastSessionRefresh = started;
|
||||||
|
this.offline = offline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdated() {
|
||||||
|
return this.updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.updated |= !Objects.equals(this.realmId, realmId);
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.updated |= !Objects.equals(this.userId, userId);
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerSessionId(String brokerSessionId) {
|
||||||
|
this.updated |= !Objects.equals(this.brokerSessionId, brokerSessionId);
|
||||||
|
this.brokerSessionId = brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return brokerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerUserId(String brokerUserId) {
|
||||||
|
this.updated |= !Objects.equals(this.brokerUserId, brokerUserId);
|
||||||
|
this.brokerUserId = brokerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLoginUsername() {
|
||||||
|
return loginUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginUsername(String loginUsername) {
|
||||||
|
this.updated |= !Objects.equals(this.loginUsername, loginUsername);
|
||||||
|
this.loginUsername = loginUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpAddress(String ipAddress) {
|
||||||
|
this.updated |= !Objects.equals(this.ipAddress, ipAddress);
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthMethod() {
|
||||||
|
return authMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthMethod(String authMethod) {
|
||||||
|
this.updated |= !Objects.equals(this.authMethod, authMethod);
|
||||||
|
this.authMethod = authMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRememberMe() {
|
||||||
|
return rememberMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRememberMe(boolean rememberMe) {
|
||||||
|
this.updated |= this.rememberMe != rememberMe;
|
||||||
|
this.rememberMe = rememberMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStarted() {
|
||||||
|
return started;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStarted(int started) {
|
||||||
|
this.updated |= this.started != started;
|
||||||
|
this.started = started;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastSessionRefresh() {
|
||||||
|
return lastSessionRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastSessionRefresh(int lastSessionRefresh) {
|
||||||
|
this.updated |= this.lastSessionRefresh != lastSessionRefresh;
|
||||||
|
this.lastSessionRefresh = lastSessionRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExpiration() {
|
||||||
|
return expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiration(long expiration) {
|
||||||
|
this.updated |= this.expiration != expiration;
|
||||||
|
this.expiration = expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getNotes() {
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNote(String name) {
|
||||||
|
return notes.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotes(Map<String, String> notes) {
|
||||||
|
this.updated |= !Objects.equals(this.notes, notes);
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String removeNote(String name) {
|
||||||
|
String note = this.notes.remove(name);
|
||||||
|
this.updated |= note != null;
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNote(String name, String value) {
|
||||||
|
this.updated |= !Objects.equals(this.notes.put(name, value), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserSessionModel.State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(UserSessionModel.State state) {
|
||||||
|
this.updated |= !Objects.equals(this.state, state);
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, K> getAuthenticatedClientSessions() {
|
||||||
|
return authenticatedClientSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticatedClientSessions(Map<String, K> authenticatedClientSessions) {
|
||||||
|
this.updated |= !Objects.equals(this.authenticatedClientSessions, authenticatedClientSessions);
|
||||||
|
this.authenticatedClientSessions = authenticatedClientSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAuthenticatedClientSession(String clientId, K clientSessionId) {
|
||||||
|
this.updated |= !Objects.equals(this.authenticatedClientSessions.put(clientId, clientSessionId), clientSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public K removeAuthenticatedClientSession(String clientId) {
|
||||||
|
K entity = this.authenticatedClientSessions.remove(clientId);
|
||||||
|
this.updated |= entity != null;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearAuthenticatedClientSessions() {
|
||||||
|
this.updated |= !authenticatedClientSessions.isEmpty();
|
||||||
|
this.authenticatedClientSessions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOffline() {
|
||||||
|
return offline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOffline(boolean offline) {
|
||||||
|
this.updated |= this.offline != offline;
|
||||||
|
this.offline = offline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserSessionModel.SessionPersistenceState getPersistenceState() {
|
||||||
|
return persistenceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState) {
|
||||||
|
this.updated |= !Objects.equals(this.persistenceState, persistenceState);
|
||||||
|
this.persistenceState = persistenceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s@%08x", getId(), hashCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractUserSessionModel<E extends AbstractEntity> implements UserSessionModel {
|
||||||
|
protected final KeycloakSession session;
|
||||||
|
protected final RealmModel realm;
|
||||||
|
protected final E entity;
|
||||||
|
|
||||||
|
public AbstractUserSessionModel(KeycloakSession session, RealmModel realm, E entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
Objects.requireNonNull(realm, "realm");
|
||||||
|
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof UserSessionModel)) return false;
|
||||||
|
|
||||||
|
UserSessionModel that = (UserSessionModel) o;
|
||||||
|
return Objects.equals(that.getId(), getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getId().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public abstract class MapAuthenticatedClientSessionAdapter extends AbstractAuthenticatedClientSessionModel<MapAuthenticatedClientSessionEntity> {
|
||||||
|
|
||||||
|
public MapAuthenticatedClientSessionAdapter(KeycloakSession session, RealmModel realm, ClientModel client,
|
||||||
|
UserSessionModel userSession, MapAuthenticatedClientSessionEntity entity) {
|
||||||
|
super(session, realm, client, userSession, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return entity.getId().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTimestamp() {
|
||||||
|
return entity.getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(int timestamp) {
|
||||||
|
entity.setTimestamp(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSession() {
|
||||||
|
return userSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCurrentRefreshToken() {
|
||||||
|
return entity.getCurrentRefreshToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentRefreshToken(String currentRefreshToken) {
|
||||||
|
entity.setCurrentRefreshToken(currentRefreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentRefreshTokenUseCount() {
|
||||||
|
return entity.getCurrentRefreshTokenUseCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) {
|
||||||
|
entity.setCurrentRefreshTokenUseCount(currentRefreshTokenUseCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNote(String name) {
|
||||||
|
return (name != null) ? entity.getNotes().get(name) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNote(String name, String value) {
|
||||||
|
if (name != null) {
|
||||||
|
if (value == null) {
|
||||||
|
entity.removeNote(name);
|
||||||
|
} else {
|
||||||
|
entity.addNote(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeNote(String name) {
|
||||||
|
if (name != null) {
|
||||||
|
entity.removeNote(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getNotes() {
|
||||||
|
return entity.getNotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRedirectUri() {
|
||||||
|
return entity.getRedirectUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRedirectUri(String uri) {
|
||||||
|
entity.setRedirectUri(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientModel getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAction() {
|
||||||
|
return entity.getAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAction(String action) {
|
||||||
|
entity.setAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocol() {
|
||||||
|
return entity.getAuthMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProtocol(String method) {
|
||||||
|
entity.setAuthMethod(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s@%08x", getId(), hashCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class MapAuthenticatedClientSessionEntity extends AbstractAuthenticatedClientSessionEntity<UUID> {
|
||||||
|
|
||||||
|
protected MapAuthenticatedClientSessionEntity() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapAuthenticatedClientSessionEntity(UUID id, String userSessionId, String realmId, String clientId, boolean offline) {
|
||||||
|
super(id, userSessionId, realmId, clientId, offline);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public abstract class MapUserSessionAdapter extends AbstractUserSessionModel<MapUserSessionEntity> {
|
||||||
|
|
||||||
|
public MapUserSessionAdapter(KeycloakSession session, RealmModel realm, MapUserSessionEntity entity) {
|
||||||
|
super(session, realm, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return entity.getId().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return entity.getBrokerSessionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return entity.getBrokerUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUser() {
|
||||||
|
return session.users().getUserById(getRealm(), entity.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLoginUsername() {
|
||||||
|
return entity.getLoginUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIpAddress() {
|
||||||
|
return entity.getIpAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthMethod() {
|
||||||
|
return entity.getAuthMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRememberMe() {
|
||||||
|
return entity.isRememberMe();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStarted() {
|
||||||
|
return entity.getStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLastSessionRefresh() {
|
||||||
|
return entity.getLastSessionRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastSessionRefresh(int seconds) {
|
||||||
|
entity.setLastSessionRefresh(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOffline() {
|
||||||
|
return entity.isOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
|
||||||
|
Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
|
||||||
|
List<String> removedClientUUIDS = new LinkedList<>();
|
||||||
|
|
||||||
|
entity.getAuthenticatedClientSessions().entrySet()
|
||||||
|
.stream()
|
||||||
|
.forEach(entry -> {
|
||||||
|
String clientUUID = entry.getKey();
|
||||||
|
ClientModel client = realm.getClientById(clientUUID);
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
|
AuthenticatedClientSessionModel clientSession = session.sessions()
|
||||||
|
.getClientSession(this, client, entry.getValue(), isOffline());
|
||||||
|
if (clientSession != null) {
|
||||||
|
result.put(clientUUID, clientSession);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removedClientUUIDS.add(clientUUID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
removeAuthenticatedClientSessions(removedClientUUIDS);
|
||||||
|
|
||||||
|
return Collections.unmodifiableMap(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticatedClientSessionModel getAuthenticatedClientSessionByClient(String clientUUID) {
|
||||||
|
UUID clientSessionId = entity.getAuthenticatedClientSessions().get(clientUUID);
|
||||||
|
|
||||||
|
if (clientSessionId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientModel client = realm.getClientById(clientUUID);
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
|
return session.sessions().getClientSession(this, client, clientSessionId, isOffline());
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAuthenticatedClientSessions(Collections.singleton(clientUUID));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNote(String name) {
|
||||||
|
return (name != null) ? entity.getNotes().get(name) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNote(String name, String value) {
|
||||||
|
if (name != null) {
|
||||||
|
if (value == null) {
|
||||||
|
entity.removeNote(name);
|
||||||
|
} else {
|
||||||
|
entity.addNote(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeNote(String name) {
|
||||||
|
if (name != null) {
|
||||||
|
entity.removeNote(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getNotes() {
|
||||||
|
return entity.getNotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public State getState() {
|
||||||
|
return entity.getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setState(State state) {
|
||||||
|
entity.setState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod,
|
||||||
|
boolean rememberMe, String brokerSessionId, String brokerUserId) {
|
||||||
|
entity.setRealmId(realm.getId());
|
||||||
|
entity.setUserId(user.getId());
|
||||||
|
entity.setLoginUsername(loginUsername);
|
||||||
|
entity.setIpAddress(ipAddress);
|
||||||
|
entity.setAuthMethod(authMethod);
|
||||||
|
entity.setRememberMe(rememberMe);
|
||||||
|
entity.setBrokerSessionId(brokerSessionId);
|
||||||
|
entity.setBrokerUserId(brokerUserId);
|
||||||
|
|
||||||
|
int currentTime = Time.currentTime();
|
||||||
|
entity.setStarted(currentTime);
|
||||||
|
entity.setLastSessionRefresh(currentTime);
|
||||||
|
|
||||||
|
entity.setState(null);
|
||||||
|
|
||||||
|
String correspondingSessionId = entity.getNote(CORRESPONDING_SESSION_ID);
|
||||||
|
entity.setNotes(new ConcurrentHashMap<>());
|
||||||
|
if (correspondingSessionId != null)
|
||||||
|
entity.addNote(CORRESPONDING_SESSION_ID, correspondingSessionId);
|
||||||
|
|
||||||
|
entity.clearAuthenticatedClientSessions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s@%08x", getId(), hashCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class MapUserSessionEntity extends AbstractUserSessionEntity<UUID> {
|
||||||
|
protected MapUserSessionEntity() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapUserSessionEntity(UUID id, String realmId) {
|
||||||
|
super(id, realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapUserSessionEntity(UUID id, RealmModel realm, UserModel user, String loginUsername, String ipAddress,
|
||||||
|
String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId,
|
||||||
|
boolean offline) {
|
||||||
|
super(id, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId, offline);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,680 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.device.DeviceActivityManager;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.map.common.Serialization;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||||
|
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
|
||||||
|
import static org.keycloak.models.UserSessionModel.SessionPersistenceState.TRANSIENT;
|
||||||
|
import static org.keycloak.models.map.userSession.SessionExpiration.setClientSessionExpiration;
|
||||||
|
import static org.keycloak.models.map.userSession.SessionExpiration.setUserSessionExpiration;
|
||||||
|
import static org.keycloak.utils.StreamsUtil.paginatedStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class MapUserSessionProvider implements UserSessionProvider {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(MapUserSessionProvider.class);
|
||||||
|
private final KeycloakSession session;
|
||||||
|
protected final MapKeycloakTransaction<UUID, MapUserSessionEntity, UserSessionModel> userSessionTx;
|
||||||
|
protected final MapKeycloakTransaction<UUID, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionTx;
|
||||||
|
private final MapStorage<UUID, MapUserSessionEntity, UserSessionModel> userSessionStore;
|
||||||
|
private final MapStorage<UUID, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage for transient user sessions which lifespan is limited to one request.
|
||||||
|
*/
|
||||||
|
private final Map<UUID, MapUserSessionEntity> transientUserSessions = new HashMap<>();
|
||||||
|
|
||||||
|
public MapUserSessionProvider(KeycloakSession session, MapStorage<UUID, MapUserSessionEntity, UserSessionModel> userSessionStore,
|
||||||
|
MapStorage<UUID, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionStore) {
|
||||||
|
this.session = session;
|
||||||
|
this.userSessionStore = userSessionStore;
|
||||||
|
this.clientSessionStore = clientSessionStore;
|
||||||
|
userSessionTx = userSessionStore.createTransaction(session);
|
||||||
|
clientSessionTx = clientSessionStore.createTransaction(session);
|
||||||
|
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(userSessionTx);
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(clientSessionTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<MapUserSessionEntity, UserSessionModel> userEntityToAdapterFunc(RealmModel realm) {
|
||||||
|
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
|
||||||
|
return (origEntity) -> {
|
||||||
|
if (origEntity.getExpiration() <= Time.currentTime()) {
|
||||||
|
if (Objects.equals(origEntity.getPersistenceState(), TRANSIENT)) {
|
||||||
|
transientUserSessions.remove(origEntity.getId());
|
||||||
|
}
|
||||||
|
userSessionTx.delete(origEntity.getId());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new MapUserSessionAdapter(session, realm,
|
||||||
|
Objects.equals(origEntity.getPersistenceState(), TRANSIENT) ? origEntity : registerEntityForChanges(origEntity)) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAuthenticatedClientSessions(Collection<String> removedClientUUIDS) {
|
||||||
|
removedClientUUIDS.forEach(entity::removeAuthenticatedClientSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastSessionRefresh(int lastSessionRefresh) {
|
||||||
|
entity.setLastSessionRefresh(lastSessionRefresh);
|
||||||
|
// whenever the lastSessionRefresh is changed recompute the expiration time
|
||||||
|
setUserSessionExpiration(entity, realm);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientEntityToAdapterFunc(RealmModel realm,
|
||||||
|
ClientModel client,
|
||||||
|
UserSessionModel userSession) {
|
||||||
|
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
|
||||||
|
return origEntity -> {
|
||||||
|
if (origEntity.getExpiration() <= Time.currentTime()) {
|
||||||
|
userSession.removeAuthenticatedClientSessions(Arrays.asList(origEntity.getClientId()));
|
||||||
|
clientSessionTx.delete(origEntity.getId());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new MapAuthenticatedClientSessionAdapter(session, realm, client, userSession, registerEntityForChanges(origEntity)) {
|
||||||
|
@Override
|
||||||
|
public void detachFromUserSession() {
|
||||||
|
this.userSession = null;
|
||||||
|
|
||||||
|
clientSessionTx.delete(entity.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(int timestamp) {
|
||||||
|
entity.setTimestamp(timestamp);
|
||||||
|
// whenever the timestamp is changed recompute the expiration time
|
||||||
|
setClientSessionExpiration(entity, realm, client);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapUserSessionEntity registerEntityForChanges(MapUserSessionEntity origEntity) {
|
||||||
|
MapUserSessionEntity res = userSessionTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
|
||||||
|
userSessionTx.updateIfChanged(origEntity.getId(), res, MapUserSessionEntity::isUpdated);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapAuthenticatedClientSessionEntity registerEntityForChanges(MapAuthenticatedClientSessionEntity origEntity) {
|
||||||
|
MapAuthenticatedClientSessionEntity res = clientSessionTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
|
||||||
|
clientSessionTx.updateIfChanged(origEntity.getId(), res, MapAuthenticatedClientSessionEntity::isUpdated);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
||||||
|
MapAuthenticatedClientSessionEntity entity =
|
||||||
|
new MapAuthenticatedClientSessionEntity(UUID.randomUUID(), userSession.getId(), realm.getId(), client.getId(), false);
|
||||||
|
setClientSessionExpiration(entity, realm, client);
|
||||||
|
|
||||||
|
LOG.tracef("createClientSession(%s, %s, %s)%s", realm, client, userSession, getShortStackTrace());
|
||||||
|
|
||||||
|
clientSessionTx.create(entity.getId(), entity);
|
||||||
|
|
||||||
|
MapUserSessionEntity userSessionEntity = getUserSessionById(UUID.fromString(userSession.getId()));
|
||||||
|
|
||||||
|
if (userSessionEntity == null) {
|
||||||
|
throw new IllegalStateException("User session entity does not exist: " + userSession.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
userSessionEntity.addAuthenticatedClientSession(client.getId(), entity.getId());
|
||||||
|
|
||||||
|
return clientEntityToAdapterFunc(realm, client, userSession).apply(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client,
|
||||||
|
UUID clientSessionId, boolean offline) {
|
||||||
|
LOG.tracef("getClientSession(%s, %s, %s, %s)%s", userSession, client,
|
||||||
|
clientSessionId, offline, getShortStackTrace());
|
||||||
|
|
||||||
|
Objects.requireNonNull(userSession, "The provided user session cannot be null!");
|
||||||
|
Objects.requireNonNull(client, "The provided client cannot be null!");
|
||||||
|
if (clientSessionId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelCriteriaBuilder<AuthenticatedClientSessionModel> mcb = clientSessionStore.createCriteriaBuilder()
|
||||||
|
.compare(AuthenticatedClientSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, clientSessionId)
|
||||||
|
.compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, ModelCriteriaBuilder.Operator.EQ, userSession.getId())
|
||||||
|
.compare(AuthenticatedClientSessionModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, userSession.getRealm().getId())
|
||||||
|
.compare(AuthenticatedClientSessionModel.SearchableFields.CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, client.getId())
|
||||||
|
.compare(AuthenticatedClientSessionModel.SearchableFields.IS_OFFLINE, ModelCriteriaBuilder.Operator.EQ, offline);
|
||||||
|
|
||||||
|
return clientSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.findFirst()
|
||||||
|
.map(clientEntityToAdapterFunc(client.getRealm(), client, userSession))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername,
|
||||||
|
String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId,
|
||||||
|
String brokerUserId, UserSessionModel.SessionPersistenceState persistenceState) {
|
||||||
|
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
|
||||||
|
|
||||||
|
LOG.tracef("createUserSession(%s, %s, %s, %s)%s", id, realm, loginUsername, persistenceState, getShortStackTrace());
|
||||||
|
|
||||||
|
MapUserSessionEntity entity = new MapUserSessionEntity(entityId, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId, false);
|
||||||
|
entity.setPersistenceState(persistenceState);
|
||||||
|
setUserSessionExpiration(entity, realm);
|
||||||
|
|
||||||
|
if (Objects.equals(persistenceState, TRANSIENT)) {
|
||||||
|
transientUserSessions.put(entityId, entity);
|
||||||
|
} else {
|
||||||
|
if (userSessionTx.read(entity.getId()) != null) {
|
||||||
|
throw new ModelDuplicateException("User session exists: " + entity.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
userSessionTx.create(entity.getId(), entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserSessionModel userSession = userEntityToAdapterFunc(realm).apply(entity);
|
||||||
|
|
||||||
|
if (userSession != null) {
|
||||||
|
DeviceActivityManager.attachDevice(userSession, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSession(RealmModel realm, String id) {
|
||||||
|
Objects.requireNonNull(realm, "The provided realm can't be null!");
|
||||||
|
|
||||||
|
LOG.tracef("getUserSession(%s, %s)%s", realm, id, getShortStackTrace());
|
||||||
|
|
||||||
|
UUID uuid = toUUID(id);
|
||||||
|
if (uuid == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapUserSessionEntity userSessionEntity = transientUserSessions.get(uuid);
|
||||||
|
if (userSessionEntity != null) {
|
||||||
|
return userEntityToAdapterFunc(realm).apply(userSessionEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false)
|
||||||
|
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uuid);
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.findFirst()
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, UserModel user) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false)
|
||||||
|
.compare(UserSessionModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, user.getId());
|
||||||
|
|
||||||
|
LOG.tracef("getUserSessionsStream(%s, %s)%s", realm, user, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false)
|
||||||
|
.compare(UserSessionModel.SearchableFields.CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, client.getId());
|
||||||
|
|
||||||
|
LOG.tracef("getUserSessionsStream(%s, %s)%s", realm, client, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client,
|
||||||
|
Integer firstResult, Integer maxResults) {
|
||||||
|
return paginatedStream(getUserSessionsStream(realm, client)
|
||||||
|
.sorted(Comparator.comparing(UserSessionModel::getLastSessionRefresh)), firstResult, maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<UserSessionModel> getUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false)
|
||||||
|
.compare(UserSessionModel.SearchableFields.BROKER_USER_ID, ModelCriteriaBuilder.Operator.EQ, brokerUserId);
|
||||||
|
|
||||||
|
LOG.tracef("getUserSessionByBrokerUserIdStream(%s, %s)%s", realm, brokerUserId, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false)
|
||||||
|
.compare(UserSessionModel.SearchableFields.BROKER_SESSION_ID, ModelCriteriaBuilder.Operator.EQ, brokerSessionId);
|
||||||
|
|
||||||
|
LOG.tracef("getUserSessionByBrokerSessionId(%s, %s)%s", realm, brokerSessionId, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.findFirst()
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSessionWithPredicate(RealmModel realm, String id, boolean offline,
|
||||||
|
Predicate<UserSessionModel> predicate) {
|
||||||
|
LOG.tracef("getUserSessionWithPredicate(%s, %s, %s)%s", realm, id, offline, getShortStackTrace());
|
||||||
|
|
||||||
|
Stream<UserSessionModel> userSessionEntityStream;
|
||||||
|
if (offline) {
|
||||||
|
userSessionEntityStream = getOfflineUserSessionEntityStream(realm, id)
|
||||||
|
.map(userEntityToAdapterFunc(realm)).filter(Objects::nonNull);
|
||||||
|
} else {
|
||||||
|
UserSessionModel userSession = getUserSession(realm, id);
|
||||||
|
userSessionEntityStream = userSession != null ? Stream.of(userSession) : Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return userSessionEntityStream
|
||||||
|
.filter(predicate)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getActiveUserSessions(RealmModel realm, ClientModel client) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false)
|
||||||
|
.compare(UserSessionModel.SearchableFields.CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, client.getId());
|
||||||
|
|
||||||
|
LOG.tracef("getActiveUserSessions(%s, %s)%s", realm, client, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getCount(mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Long> getActiveClientSessionStats(RealmModel realm, boolean offline) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, offline);
|
||||||
|
|
||||||
|
LOG.tracef("getActiveClientSessionStats(%s, %s)%s", realm, offline, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(UserSessionModel::getAuthenticatedClientSessions)
|
||||||
|
.map(Map::keySet)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUserSession(RealmModel realm, UserSessionModel session) {
|
||||||
|
Objects.requireNonNull(session, "The provided user session can't be null!");
|
||||||
|
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false)
|
||||||
|
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, UUID.fromString(session.getId()));
|
||||||
|
|
||||||
|
LOG.tracef("removeUserSession(%s, %s)%s", realm, session, getShortStackTrace());
|
||||||
|
|
||||||
|
userSessionTx.delete(UUID.randomUUID(), mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUserSessions(RealmModel realm, UserModel user) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = userSessionStore.createCriteriaBuilder()
|
||||||
|
.compare(UserSessionModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())
|
||||||
|
.compare(UserSessionModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, user.getId());
|
||||||
|
|
||||||
|
LOG.tracef("removeUserSessions(%s, %s)%s", realm, user, getShortStackTrace());
|
||||||
|
|
||||||
|
userSessionTx.delete(UUID.randomUUID(), mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAllExpired() {
|
||||||
|
LOG.tracef("removeAllExpired()%s", getShortStackTrace());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeExpired(RealmModel realm) {
|
||||||
|
LOG.tracef("removeExpired(%s)%s", realm, getShortStackTrace());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUserSessions(RealmModel realm) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false);
|
||||||
|
|
||||||
|
LOG.tracef("removeUserSessions(%s)%s", realm, getShortStackTrace());
|
||||||
|
|
||||||
|
userSessionTx.delete(UUID.randomUUID(), mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRealmRemoved(RealmModel realm) {
|
||||||
|
LOG.tracef("onRealmRemoved(%s)%s", realm, getShortStackTrace());
|
||||||
|
|
||||||
|
removeUserSessions(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
|
||||||
|
LOG.tracef("createOfflineUserSession(%s)%s", userSession, getShortStackTrace());
|
||||||
|
|
||||||
|
MapUserSessionEntity offlineUserSession = createUserSessionEntityInstance(userSession, true);
|
||||||
|
|
||||||
|
// set a reference for the offline user session to the original online user session
|
||||||
|
userSession.setNote(CORRESPONDING_SESSION_ID, offlineUserSession.getId().toString());
|
||||||
|
|
||||||
|
int currentTime = Time.currentTime();
|
||||||
|
offlineUserSession.setStarted(currentTime);
|
||||||
|
offlineUserSession.setLastSessionRefresh(currentTime);
|
||||||
|
setUserSessionExpiration(offlineUserSession, userSession.getRealm());
|
||||||
|
|
||||||
|
userSessionTx.create(offlineUserSession.getId(), offlineUserSession);
|
||||||
|
|
||||||
|
return userEntityToAdapterFunc(userSession.getRealm()).apply(offlineUserSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
|
||||||
|
LOG.tracef("getOfflineUserSession(%s, %s)%s", realm, userSessionId, getShortStackTrace());
|
||||||
|
|
||||||
|
return getOfflineUserSessionEntityStream(realm, userSessionId)
|
||||||
|
.findFirst()
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession) {
|
||||||
|
Objects.requireNonNull(userSession, "The provided user session can't be null!");
|
||||||
|
|
||||||
|
LOG.tracef("removeOfflineUserSession(%s, %s)%s", realm, userSession, getShortStackTrace());
|
||||||
|
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb;
|
||||||
|
if (userSession.isOffline()) {
|
||||||
|
userSessionTx.delete(UUID.fromString(userSession.getId()));
|
||||||
|
} else if (userSession.getNote(CORRESPONDING_SESSION_ID) != null) {
|
||||||
|
mcb = realmAndOfflineCriteriaBuilder(realm, true)
|
||||||
|
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, UUID.fromString(userSession.getNote(CORRESPONDING_SESSION_ID)));
|
||||||
|
userSessionTx.delete(UUID.randomUUID(), mcb);
|
||||||
|
userSession.removeNote(CORRESPONDING_SESSION_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession,
|
||||||
|
UserSessionModel offlineUserSession) {
|
||||||
|
LOG.tracef("createOfflineClientSession(%s, %s)%s", clientSession, offlineUserSession, getShortStackTrace());
|
||||||
|
|
||||||
|
MapAuthenticatedClientSessionEntity clientSessionEntity = createAuthenticatedClientSessionInstance(clientSession, offlineUserSession, true);
|
||||||
|
clientSessionEntity.setTimestamp(Time.currentTime());
|
||||||
|
setClientSessionExpiration(clientSessionEntity, clientSession.getRealm(), clientSession.getClient());
|
||||||
|
|
||||||
|
Optional<MapUserSessionEntity> userSessionEntity = getOfflineUserSessionEntityStream(clientSession.getRealm(), offlineUserSession.getId()).findFirst();
|
||||||
|
if (userSessionEntity.isPresent()) {
|
||||||
|
userSessionEntity.get().addAuthenticatedClientSession(clientSession.getClient().getId(), clientSessionEntity.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSessionTx.create(clientSessionEntity.getId(), clientSessionEntity);
|
||||||
|
|
||||||
|
return clientEntityToAdapterFunc(clientSession.getRealm(),
|
||||||
|
clientSession.getClient(), offlineUserSession).apply(clientSessionEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, UserModel user) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, true)
|
||||||
|
.compare(UserSessionModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, user.getId());
|
||||||
|
|
||||||
|
LOG.tracef("getOfflineUserSessionsStream(%s, %s)%s", realm, user, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getOfflineUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, true)
|
||||||
|
.compare(UserSessionModel.SearchableFields.BROKER_SESSION_ID, ModelCriteriaBuilder.Operator.EQ, brokerSessionId);
|
||||||
|
|
||||||
|
LOG.tracef("getOfflineUserSessionByBrokerSessionId(%s, %s)%s", realm, brokerSessionId, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.findFirst()
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<UserSessionModel> getOfflineUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, true)
|
||||||
|
.compare(UserSessionModel.SearchableFields.BROKER_USER_ID, ModelCriteriaBuilder.Operator.EQ, brokerUserId);
|
||||||
|
|
||||||
|
LOG.tracef("getOfflineUserSessionByBrokerUserIdStream(%s, %s)%s", realm, brokerUserId, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getOfflineSessionsCount(RealmModel realm, ClientModel client) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, true)
|
||||||
|
.compare(UserSessionModel.SearchableFields.CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, client.getId());
|
||||||
|
|
||||||
|
LOG.tracef("getOfflineSessionsCount(%s, %s)%s", realm, client, getShortStackTrace());
|
||||||
|
|
||||||
|
return userSessionTx.getCount(mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, ClientModel client,
|
||||||
|
Integer firstResult, Integer maxResults) {
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, true)
|
||||||
|
.compare(UserSessionModel.SearchableFields.CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, client.getId());
|
||||||
|
|
||||||
|
LOG.tracef("getOfflineUserSessionsStream(%s, %s, %s, %s)%s", realm, client, firstResult, maxResults, getShortStackTrace());
|
||||||
|
|
||||||
|
return paginatedStream(userSessionTx.getUpdatedNotRemoved(mcb)
|
||||||
|
.map(userEntityToAdapterFunc(realm))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted(Comparator.comparing(UserSessionModel::getLastSessionRefresh)), firstResult, maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importUserSessions(Collection<UserSessionModel> persistentUserSessions, boolean offline) {
|
||||||
|
if (persistentUserSessions == null || persistentUserSessions.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
persistentUserSessions.stream()
|
||||||
|
.map(pus -> {
|
||||||
|
MapUserSessionEntity userSessionEntity = new MapUserSessionEntity(UUID.randomUUID(), pus.getRealm(), pus.getUser(),
|
||||||
|
pus.getLoginUsername(), pus.getIpAddress(), pus.getAuthMethod(),
|
||||||
|
pus.isRememberMe(), pus.getBrokerSessionId(), pus.getBrokerUserId(), offline);
|
||||||
|
|
||||||
|
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : pus.getAuthenticatedClientSessions().entrySet()) {
|
||||||
|
MapAuthenticatedClientSessionEntity clientSession = createAuthenticatedClientSessionInstance(entry.getValue(), entry.getValue().getUserSession(), offline);
|
||||||
|
|
||||||
|
// Update timestamp to same value as userSession. LastSessionRefresh of userSession from DB will have correct value
|
||||||
|
clientSession.setTimestamp(userSessionEntity.getLastSessionRefresh());
|
||||||
|
|
||||||
|
userSessionEntity.addAuthenticatedClientSession(entry.getKey(), clientSession.getId());
|
||||||
|
|
||||||
|
clientSessionTx.create(clientSession.getId(), clientSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userSessionEntity;
|
||||||
|
})
|
||||||
|
.forEach(use -> userSessionTx.create(use.getId(), use));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<MapUserSessionEntity> getOfflineUserSessionEntityStream(RealmModel realm, String userSessionId) {
|
||||||
|
UUID uuid = toUUID(userSessionId);
|
||||||
|
if (uuid == null) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// first get a user entity by ID
|
||||||
|
ModelCriteriaBuilder<UserSessionModel> mcb = userSessionStore.createCriteriaBuilder()
|
||||||
|
.compare(UserSessionModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())
|
||||||
|
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uuid);
|
||||||
|
|
||||||
|
// check if it's an offline user session
|
||||||
|
MapUserSessionEntity userSessionEntity = userSessionTx.getUpdatedNotRemoved(mcb).findFirst().orElse(null);
|
||||||
|
if (userSessionEntity != null) {
|
||||||
|
if (userSessionEntity.isOffline()) {
|
||||||
|
return Stream.of(userSessionEntity);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no session found by the given ID, try to find by corresponding session ID
|
||||||
|
mcb = realmAndOfflineCriteriaBuilder(realm, true)
|
||||||
|
.compare(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, ModelCriteriaBuilder.Operator.EQ, userSessionId);
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's online user session so lookup offline user session by corresponding session id reference
|
||||||
|
String offlineUserSessionId = userSessionEntity.getNote(CORRESPONDING_SESSION_ID);
|
||||||
|
if (offlineUserSessionId != null) {
|
||||||
|
mcb = realmAndOfflineCriteriaBuilder(realm, true)
|
||||||
|
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, UUID.fromString(offlineUserSessionId));
|
||||||
|
return userSessionTx.getUpdatedNotRemoved(mcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModelCriteriaBuilder<UserSessionModel> realmAndOfflineCriteriaBuilder(RealmModel realm, boolean offline) {
|
||||||
|
return userSessionStore.createCriteriaBuilder()
|
||||||
|
.compare(UserSessionModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())
|
||||||
|
.compare(UserSessionModel.SearchableFields.IS_OFFLINE, ModelCriteriaBuilder.Operator.EQ, offline);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapUserSessionEntity getUserSessionById(UUID id) {
|
||||||
|
MapUserSessionEntity userSessionEntity = transientUserSessions.get(id);
|
||||||
|
|
||||||
|
if (userSessionEntity == null) {
|
||||||
|
MapUserSessionEntity userSession = userSessionTx.read(id);
|
||||||
|
return userSession != null ? registerEntityForChanges(userSession) : null;
|
||||||
|
}
|
||||||
|
return userSessionEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapUserSessionEntity createUserSessionEntityInstance(UserSessionModel userSession, boolean offline) {
|
||||||
|
MapUserSessionEntity entity = new MapUserSessionEntity(UUID.randomUUID(), userSession.getRealm().getId());
|
||||||
|
|
||||||
|
entity.setAuthMethod(userSession.getAuthMethod());
|
||||||
|
entity.setBrokerSessionId(userSession.getBrokerSessionId());
|
||||||
|
entity.setBrokerUserId(userSession.getBrokerUserId());
|
||||||
|
entity.setIpAddress(userSession.getIpAddress());
|
||||||
|
entity.setNotes(new ConcurrentHashMap<>(userSession.getNotes()));
|
||||||
|
entity.addNote(CORRESPONDING_SESSION_ID, userSession.getId());
|
||||||
|
|
||||||
|
entity.clearAuthenticatedClientSessions();
|
||||||
|
entity.setRememberMe(userSession.isRememberMe());
|
||||||
|
entity.setState(userSession.getState());
|
||||||
|
entity.setLoginUsername(userSession.getLoginUsername());
|
||||||
|
entity.setUserId(userSession.getUser().getId());
|
||||||
|
|
||||||
|
entity.setStarted(userSession.getStarted());
|
||||||
|
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
|
||||||
|
entity.setOffline(offline);
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapAuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(AuthenticatedClientSessionModel clientSession,
|
||||||
|
UserSessionModel userSession, boolean offline) {
|
||||||
|
MapAuthenticatedClientSessionEntity entity = new MapAuthenticatedClientSessionEntity(UUID.randomUUID(),
|
||||||
|
userSession.getId(), clientSession.getRealm().getId(), clientSession.getClient().getId(), offline);
|
||||||
|
|
||||||
|
entity.setAction(clientSession.getAction());
|
||||||
|
entity.setAuthMethod(clientSession.getProtocol());
|
||||||
|
|
||||||
|
entity.setNotes(new ConcurrentHashMap<>(clientSession.getNotes()));
|
||||||
|
entity.setRedirectUri(clientSession.getRedirectUri());
|
||||||
|
entity.setTimestamp(clientSession.getTimestamp());
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID toUUID(String id) {
|
||||||
|
try {
|
||||||
|
return UUID.fromString(id);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.UserSessionProviderFactory;
|
||||||
|
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class MapUserSessionProviderFactory extends AbstractMapProviderFactory<UserSessionProvider>
|
||||||
|
implements UserSessionProviderFactory {
|
||||||
|
|
||||||
|
private MapStorage<UUID, MapUserSessionEntity, UserSessionModel> userSessionStore;
|
||||||
|
private MapStorage<UUID, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionStore;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
||||||
|
userSessionStore = sp.getStorage("userSessions", UUID.class, MapUserSessionEntity.class, UserSessionModel.class);
|
||||||
|
clientSessionStore = sp.getStorage("clientSessions", UUID.class, MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
|
||||||
|
|
||||||
|
factory.register(event -> {
|
||||||
|
if (event instanceof UserModel.UserRemovedEvent) {
|
||||||
|
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
|
||||||
|
|
||||||
|
MapUserSessionProvider provider = MapUserSessionProviderFactory.this.create(userRemovedEvent.getKeycloakSession());
|
||||||
|
provider.removeUserSessions(userRemovedEvent.getRealm(), userRemovedEvent.getUser());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadPersistentSessions(KeycloakSessionFactory sessionFactory, int maxErrors, int sessionsPerSegment) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapUserSessionProvider create(KeycloakSession session) {
|
||||||
|
return new MapUserSessionProvider(session, userSessionStore, clientSessionStore);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.map.userSession;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class SessionExpiration {
|
||||||
|
|
||||||
|
public static void setClientSessionExpiration(MapAuthenticatedClientSessionEntity entity, RealmModel realm, ClientModel client) {
|
||||||
|
if (entity.isOffline()) {
|
||||||
|
long sessionExpires = entity.getTimestamp() + realm.getOfflineSessionIdleTimeout();
|
||||||
|
if (realm.isOfflineSessionMaxLifespanEnabled()) {
|
||||||
|
sessionExpires = entity.getTimestamp() + realm.getOfflineSessionMaxLifespan();
|
||||||
|
|
||||||
|
long clientOfflineSessionMaxLifespan;
|
||||||
|
String clientOfflineSessionMaxLifespanPerClient = client.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN);
|
||||||
|
if (clientOfflineSessionMaxLifespanPerClient != null && !clientOfflineSessionMaxLifespanPerClient.trim().isEmpty()) {
|
||||||
|
clientOfflineSessionMaxLifespan = Long.parseLong(clientOfflineSessionMaxLifespanPerClient);
|
||||||
|
} else {
|
||||||
|
clientOfflineSessionMaxLifespan = realm.getClientOfflineSessionMaxLifespan();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientOfflineSessionMaxLifespan > 0) {
|
||||||
|
long clientOfflineSessionMaxExpiration = entity.getTimestamp() + clientOfflineSessionMaxLifespan;
|
||||||
|
sessionExpires = Math.min(sessionExpires, clientOfflineSessionMaxExpiration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long expiration = entity.getTimestamp() + realm.getOfflineSessionIdleTimeout();
|
||||||
|
|
||||||
|
long clientOfflineSessionIdleTimeout;
|
||||||
|
String clientOfflineSessionIdleTimeoutPerClient = client.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT);
|
||||||
|
if (clientOfflineSessionIdleTimeoutPerClient != null && !clientOfflineSessionIdleTimeoutPerClient.trim().isEmpty()) {
|
||||||
|
clientOfflineSessionIdleTimeout = Long.parseLong(clientOfflineSessionIdleTimeoutPerClient);
|
||||||
|
} else {
|
||||||
|
clientOfflineSessionIdleTimeout = realm.getClientOfflineSessionIdleTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientOfflineSessionIdleTimeout > 0) {
|
||||||
|
long clientOfflineSessionIdleExpiration = entity.getTimestamp() + clientOfflineSessionIdleTimeout;
|
||||||
|
expiration = Math.min(expiration, clientOfflineSessionIdleExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setExpiration(Math.min(expiration, sessionExpires));
|
||||||
|
} else {
|
||||||
|
long sessionExpires = (long) entity.getTimestamp() + (realm.getSsoSessionMaxLifespanRememberMe() > 0
|
||||||
|
? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan());
|
||||||
|
|
||||||
|
long clientSessionMaxLifespan;
|
||||||
|
String clientSessionMaxLifespanPerClient = client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN);
|
||||||
|
if (clientSessionMaxLifespanPerClient != null && !clientSessionMaxLifespanPerClient.trim().isEmpty()) {
|
||||||
|
clientSessionMaxLifespan = Long.parseLong(clientSessionMaxLifespanPerClient);
|
||||||
|
} else {
|
||||||
|
clientSessionMaxLifespan = realm.getClientSessionMaxLifespan();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientSessionMaxLifespan > 0) {
|
||||||
|
long clientSessionMaxExpiration = entity.getTimestamp() + clientSessionMaxLifespan;
|
||||||
|
sessionExpires = Math.min(sessionExpires, clientSessionMaxExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
long expiration = (long) entity.getTimestamp() + (realm.getSsoSessionIdleTimeoutRememberMe() > 0
|
||||||
|
? realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout());
|
||||||
|
|
||||||
|
long clientSessionIdleTimeout;
|
||||||
|
String clientSessionIdleTimeoutPerClient = client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT);
|
||||||
|
if (clientSessionIdleTimeoutPerClient != null && !clientSessionIdleTimeoutPerClient.trim().isEmpty()) {
|
||||||
|
clientSessionIdleTimeout = Long.parseLong(clientSessionIdleTimeoutPerClient);
|
||||||
|
} else {
|
||||||
|
clientSessionIdleTimeout = realm.getClientSessionIdleTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientSessionIdleTimeout > 0) {
|
||||||
|
long clientSessionIdleExpiration = entity.getTimestamp() + clientSessionIdleTimeout;
|
||||||
|
expiration = Math.min(expiration, clientSessionIdleExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setExpiration(Math.min(expiration, sessionExpires));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUserSessionExpiration(MapUserSessionEntity entity, RealmModel realm) {
|
||||||
|
if (entity.isOffline()) {
|
||||||
|
long sessionExpires = entity.getLastSessionRefresh() + realm.getOfflineSessionIdleTimeout();
|
||||||
|
if (realm.isOfflineSessionMaxLifespanEnabled()) {
|
||||||
|
sessionExpires = entity.getStarted() + realm.getOfflineSessionMaxLifespan();
|
||||||
|
|
||||||
|
long clientOfflineSessionMaxLifespan = realm.getClientOfflineSessionMaxLifespan();
|
||||||
|
|
||||||
|
if (clientOfflineSessionMaxLifespan > 0) {
|
||||||
|
long clientOfflineSessionMaxExpiration = entity.getStarted() + clientOfflineSessionMaxLifespan;
|
||||||
|
sessionExpires = Math.min(sessionExpires, clientOfflineSessionMaxExpiration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long expiration = entity.getLastSessionRefresh() + realm.getOfflineSessionIdleTimeout();
|
||||||
|
|
||||||
|
long clientOfflineSessionIdleTimeout = realm.getClientOfflineSessionIdleTimeout();
|
||||||
|
|
||||||
|
if (clientOfflineSessionIdleTimeout > 0) {
|
||||||
|
long clientOfflineSessionIdleExpiration = Time.currentTime() + clientOfflineSessionIdleTimeout;
|
||||||
|
expiration = Math.min(expiration, clientOfflineSessionIdleExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setExpiration(Math.min(expiration, sessionExpires));
|
||||||
|
} else {
|
||||||
|
long sessionExpires = (long) entity.getStarted()
|
||||||
|
+ (entity.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0
|
||||||
|
? realm.getSsoSessionMaxLifespanRememberMe()
|
||||||
|
: realm.getSsoSessionMaxLifespan());
|
||||||
|
|
||||||
|
long clientSessionMaxLifespan = realm.getClientSessionMaxLifespan();
|
||||||
|
|
||||||
|
if (clientSessionMaxLifespan > 0) {
|
||||||
|
long clientSessionMaxExpiration = entity.getStarted() + clientSessionMaxLifespan;
|
||||||
|
sessionExpires = Math.min(sessionExpires, clientSessionMaxExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
long expiration = (long) entity.getLastSessionRefresh() + (entity.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0
|
||||||
|
? realm.getSsoSessionIdleTimeoutRememberMe()
|
||||||
|
: realm.getSsoSessionIdleTimeout());
|
||||||
|
|
||||||
|
long clientSessionIdleTimeout = realm.getClientSessionIdleTimeout();
|
||||||
|
|
||||||
|
if (clientSessionIdleTimeout > 0) {
|
||||||
|
long clientSessionIdleExpiration = entity.getLastSessionRefresh() + clientSessionIdleTimeout;
|
||||||
|
expiration = Math.min(expiration, clientSessionIdleExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setExpiration(Math.min(expiration, sessionExpires));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2021 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2021 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.models.map.userSession.MapUserSessionProviderFactory
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public interface UserLoginFailureProviderFactory extends ProviderFactory<UserLoginFailureProvider> {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class UserLoginFailureSpi implements Spi {
|
||||||
|
|
||||||
|
public static final String NAME = "loginFailure";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return UserLoginFailureProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return UserLoginFailureProviderFactory.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ org.keycloak.models.SamlArtifactSessionMappingStoreSpi
|
||||||
org.keycloak.models.SingleUseTokenStoreSpi
|
org.keycloak.models.SingleUseTokenStoreSpi
|
||||||
org.keycloak.models.TokenRevocationStoreSpi
|
org.keycloak.models.TokenRevocationStoreSpi
|
||||||
org.keycloak.models.UserSessionSpi
|
org.keycloak.models.UserSessionSpi
|
||||||
|
org.keycloak.models.UserLoginFailureSpi
|
||||||
org.keycloak.models.UserSpi
|
org.keycloak.models.UserSpi
|
||||||
org.keycloak.models.session.UserSessionPersisterSpi
|
org.keycloak.models.session.UserSessionPersisterSpi
|
||||||
org.keycloak.models.dblock.DBLockSpi
|
org.keycloak.models.dblock.DBLockSpi
|
||||||
|
|
|
@ -21,12 +21,22 @@ package org.keycloak.models;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.sessions.CommonClientSessionModel;
|
import org.keycloak.sessions.CommonClientSessionModel;
|
||||||
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public interface AuthenticatedClientSessionModel extends CommonClientSessionModel {
|
public interface AuthenticatedClientSessionModel extends CommonClientSessionModel {
|
||||||
|
|
||||||
|
class SearchableFields {
|
||||||
|
public static final SearchableModelField<AuthenticatedClientSessionModel> ID = new SearchableModelField<>("id", String.class);
|
||||||
|
public static final SearchableModelField<AuthenticatedClientSessionModel> REALM_ID = new SearchableModelField<>("realmId", String.class);
|
||||||
|
public static final SearchableModelField<AuthenticatedClientSessionModel> CLIENT_ID = new SearchableModelField<>("clientId", String.class);
|
||||||
|
public static final SearchableModelField<AuthenticatedClientSessionModel> USER_SESSION_ID = new SearchableModelField<>("userSessionId", String.class);
|
||||||
|
public static final SearchableModelField<AuthenticatedClientSessionModel> IS_OFFLINE = new SearchableModelField<>("isOffline", Boolean.class);
|
||||||
|
public static final SearchableModelField<AuthenticatedClientSessionModel> TIMESTAMP = new SearchableModelField<>("timestamp", Integer.class);
|
||||||
|
}
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
int getTimestamp();
|
int getTimestamp();
|
||||||
|
|
|
@ -20,7 +20,6 @@ package org.keycloak.models;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.provider.InvalidationHandler;
|
import org.keycloak.provider.InvalidationHandler;
|
||||||
import org.keycloak.provider.InvalidationHandler.InvalidableObjectType;
|
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
||||||
import org.keycloak.sessions.AuthenticationSessionProvider;
|
import org.keycloak.sessions.AuthenticationSessionProvider;
|
||||||
|
@ -178,6 +177,14 @@ public interface KeycloakSession extends InvalidationHandler {
|
||||||
*/
|
*/
|
||||||
UserSessionProvider sessions();
|
UserSessionProvider sessions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a managed provider instance. Will start a provider transaction. This transaction is managed by the KeycloakSession
|
||||||
|
* transaction.
|
||||||
|
*
|
||||||
|
* @return {@link UserLoginFailureProvider}
|
||||||
|
* @throws IllegalStateException if transaction is not active
|
||||||
|
*/
|
||||||
|
UserLoginFailureProvider loginFailures();
|
||||||
|
|
||||||
AuthenticationSessionProvider authenticationSessions();
|
AuthenticationSessionProvider authenticationSessions();
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,20 @@
|
||||||
|
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface UserLoginFailureModel
|
public interface UserLoginFailureModel {
|
||||||
{
|
|
||||||
|
class SearchableFields {
|
||||||
|
public static final SearchableModelField<UserLoginFailureModel> ID = new SearchableModelField<>("id", String.class);
|
||||||
|
public static final SearchableModelField<UserLoginFailureModel> REALM_ID = new SearchableModelField<>("realmId", String.class);
|
||||||
|
public static final SearchableModelField<UserLoginFailureModel> USER_ID = new SearchableModelField<>("userId", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
String getUserId();
|
String getUserId();
|
||||||
int getFailedLoginNotBefore();
|
int getFailedLoginNotBefore();
|
||||||
void setFailedLoginNotBefore(int notBefore);
|
void setFailedLoginNotBefore(int notBefore);
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public interface UserLoginFailureProvider extends Provider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link UserLoginFailureModel} for the given realm and user id.
|
||||||
|
* @param realm {@link RealmModel}
|
||||||
|
* @param userId {@link String} Id of the user.
|
||||||
|
* @return Returns the {@link UserLoginFailureModel} for the given realm and user id.
|
||||||
|
*/
|
||||||
|
UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link UserLoginFailureModel} for the given realm and user id.
|
||||||
|
* @param realm {@link RealmModel}
|
||||||
|
* @param userId {@link String} Id of the user.
|
||||||
|
* @return Returns newly created {@link UserLoginFailureModel}.
|
||||||
|
*/
|
||||||
|
UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a {@link UserLoginFailureModel} for the given realm and user id.
|
||||||
|
* @param realm {@link RealmModel}
|
||||||
|
* @param userId {@link String} Id of the user.
|
||||||
|
*/
|
||||||
|
void removeUserLoginFailure(RealmModel realm, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all the {@link UserLoginFailureModel} for the given realm.
|
||||||
|
* @param realm {@link RealmModel}
|
||||||
|
*/
|
||||||
|
void removeAllUserLoginFailures(RealmModel realm);
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -25,6 +27,28 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public interface UserSessionModel {
|
public interface UserSessionModel {
|
||||||
|
|
||||||
|
class SearchableFields {
|
||||||
|
public static final SearchableModelField<UserSessionModel> ID = new SearchableModelField<>("id", String.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the corresponding offline user session for the online user session.
|
||||||
|
* null if there is no corresponding offline user session.
|
||||||
|
*/
|
||||||
|
public static final SearchableModelField<UserSessionModel> CORRESPONDING_SESSION_ID = new SearchableModelField<>("correspondingSessionId", String.class);
|
||||||
|
public static final SearchableModelField<UserSessionModel> REALM_ID = new SearchableModelField<>("realmId", String.class);
|
||||||
|
public static final SearchableModelField<UserSessionModel> USER_ID = new SearchableModelField<>("userId", String.class);
|
||||||
|
public static final SearchableModelField<UserSessionModel> CLIENT_ID = new SearchableModelField<>("clientId", String.class);
|
||||||
|
public static final SearchableModelField<UserSessionModel> BROKER_SESSION_ID = new SearchableModelField<>("brokerSessionId", String.class);
|
||||||
|
public static final SearchableModelField<UserSessionModel> BROKER_USER_ID = new SearchableModelField<>("brokerUserId", String.class);
|
||||||
|
public static final SearchableModelField<UserSessionModel> IS_OFFLINE = new SearchableModelField<>("isOffline", Boolean.class);
|
||||||
|
public static final SearchableModelField<UserSessionModel> LAST_SESSION_REFRESH = new SearchableModelField<>("lastSessionRefresh", Integer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the corresponding online/offline user session.
|
||||||
|
*/
|
||||||
|
String CORRESPONDING_SESSION_ID = "correspondingSessionId";
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
RealmModel getRealm();
|
RealmModel getRealm();
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,12 @@ import java.util.stream.Stream;
|
||||||
*/
|
*/
|
||||||
public interface UserSessionProvider extends Provider {
|
public interface UserSessionProvider extends Provider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns currently used Keycloak session.
|
||||||
|
* @return {@link KeycloakSession}
|
||||||
|
*/
|
||||||
|
KeycloakSession getKeycloakSession();
|
||||||
|
|
||||||
AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession);
|
AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession);
|
||||||
AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline);
|
AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline);
|
||||||
|
|
||||||
|
@ -52,11 +58,11 @@ public interface UserSessionProvider extends Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the user sessions associated with the specified user.
|
* Obtains the online user sessions associated with the specified user.
|
||||||
*
|
*
|
||||||
* @param realm a reference to the realm.
|
* @param realm a reference to the realm.
|
||||||
* @param user the user whose sessions are being searched.
|
* @param user the user whose sessions are being searched.
|
||||||
* @return a non-null {@link Stream} of user sessions.
|
* @return a non-null {@link Stream} of online user sessions.
|
||||||
*/
|
*/
|
||||||
Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, UserModel user);
|
Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
|
@ -69,11 +75,11 @@ public interface UserSessionProvider extends Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the user sessions associated with the specified client.
|
* Obtains the online user sessions associated with the specified client.
|
||||||
*
|
*
|
||||||
* @param realm a reference to the realm.
|
* @param realm a reference to the realm.
|
||||||
* @param client the client whose user sessions are being searched.
|
* @param client the client whose user sessions are being searched.
|
||||||
* @return a non-null {@link Stream} of user sessions.
|
* @return a non-null {@link Stream} of online user sessions.
|
||||||
*/
|
*/
|
||||||
Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client);
|
Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client);
|
||||||
|
|
||||||
|
@ -86,14 +92,14 @@ public interface UserSessionProvider extends Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the user sessions associated with the specified client, starting from the {@code firstResult} and containing
|
* Obtains the online user sessions associated with the specified client, starting from the {@code firstResult} and containing
|
||||||
* at most {@code maxResults}.
|
* at most {@code maxResults}.
|
||||||
*
|
*
|
||||||
* @param realm a reference tot he realm.
|
* @param realm a reference tot he realm.
|
||||||
* @param client the client whose user sessions are being searched.
|
* @param client the client whose user sessions are being searched.
|
||||||
* @param firstResult first result to return. Ignored if negative or {@code null}.
|
* @param firstResult first result to return. Ignored if negative or {@code null}.
|
||||||
* @param maxResults maximum number of results to return. Ignored if negative or {@code null}.
|
* @param maxResults maximum number of results to return. Ignored if negative or {@code null}.
|
||||||
* @return a non-null {@link Stream} of user sessions.
|
* @return a non-null {@link Stream} of online user sessions.
|
||||||
*/
|
*/
|
||||||
Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client, Integer firstResult, Integer maxResults);
|
Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, ClientModel client, Integer firstResult, Integer maxResults);
|
||||||
|
|
||||||
|
@ -107,11 +113,11 @@ public interface UserSessionProvider extends Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the user sessions associated with the user that matches the specified {@code brokerUserId}.
|
* Obtains the online user sessions associated with the user that matches the specified {@code brokerUserId}.
|
||||||
*
|
*
|
||||||
* @param realm a reference to the realm.
|
* @param realm a reference to the realm.
|
||||||
* @param brokerUserId the id of the broker user whose sessions are being searched.
|
* @param brokerUserId the id of the broker user whose sessions are being searched.
|
||||||
* @return a non-null {@link Stream} of user sessions.
|
* @return a non-null {@link Stream} of online user sessions.
|
||||||
*/
|
*/
|
||||||
Stream<UserSessionModel> getUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId);
|
Stream<UserSessionModel> getUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId);
|
||||||
|
|
||||||
|
@ -154,10 +160,37 @@ public interface UserSessionProvider extends Provider {
|
||||||
|
|
||||||
void removeUserSessions(RealmModel realm);
|
void removeUserSessions(RealmModel realm);
|
||||||
|
|
||||||
UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId);
|
/**
|
||||||
UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId);
|
* @deprecated Use {@link UserLoginFailureProvider#getUserLoginFailure(RealmModel, String) getUserLoginFailure} instead.
|
||||||
void removeUserLoginFailure(RealmModel realm, String userId);
|
*/
|
||||||
void removeAllUserLoginFailures(RealmModel realm);
|
@Deprecated
|
||||||
|
default UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
return getKeycloakSession().loginFailures().getUserLoginFailure(realm, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link UserLoginFailureProvider#addUserLoginFailure(RealmModel, String) addUserLoginFailure} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
default UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
return getKeycloakSession().loginFailures().addUserLoginFailure(realm, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link UserLoginFailureProvider#removeUserLoginFailure(RealmModel, String) removeUserLoginFailure} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
default void removeUserLoginFailure(RealmModel realm, String userId) {
|
||||||
|
getKeycloakSession().loginFailures().removeUserLoginFailure(realm, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link UserLoginFailureProvider#removeAllUserLoginFailures(RealmModel) removeAllUserLoginFailures} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
default void removeAllUserLoginFailures(RealmModel realm) {
|
||||||
|
getKeycloakSession().loginFailures().removeAllUserLoginFailures(realm);
|
||||||
|
}
|
||||||
|
|
||||||
void onRealmRemoved(RealmModel realm);
|
void onRealmRemoved(RealmModel realm);
|
||||||
void onClientRemoved(RealmModel realm, ClientModel client);
|
void onClientRemoved(RealmModel realm, ClientModel client);
|
||||||
|
|
|
@ -49,7 +49,6 @@ import org.keycloak.models.TokenRevocationStoreProvider;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.UserSessionProvider;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.RoleUtils;
|
import org.keycloak.models.utils.RoleUtils;
|
||||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||||
|
@ -92,7 +91,6 @@ import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import static org.keycloak.representations.IDToken.NONCE;
|
import static org.keycloak.representations.IDToken.NONCE;
|
||||||
import static org.keycloak.representations.IDToken.PHONE_NUMBER;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stateless object that creates tokens and manages oauth access codes
|
* Stateless object that creates tokens and manages oauth access codes
|
||||||
|
@ -265,7 +263,12 @@ public class TokenManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
userSession.setLastSessionRefresh(Time.currentTime());
|
int currentTime = Time.currentTime();
|
||||||
|
userSession.setLastSessionRefresh(currentTime);
|
||||||
|
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||||
|
if (clientSession != null) {
|
||||||
|
clientSession.setTimestamp(currentTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.keys.DefaultKeyManager;
|
||||||
import org.keycloak.models.ClientProvider;
|
import org.keycloak.models.ClientProvider;
|
||||||
import org.keycloak.models.ClientScopeProvider;
|
import org.keycloak.models.ClientScopeProvider;
|
||||||
import org.keycloak.models.GroupProvider;
|
import org.keycloak.models.GroupProvider;
|
||||||
|
import org.keycloak.models.UserLoginFailureProvider;
|
||||||
import org.keycloak.models.TokenManager;
|
import org.keycloak.models.TokenManager;
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -40,7 +41,6 @@ import org.keycloak.models.UserSessionProvider;
|
||||||
import org.keycloak.models.cache.CacheRealmProvider;
|
import org.keycloak.models.cache.CacheRealmProvider;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.provider.InvalidationHandler.InvalidableObjectType;
|
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
||||||
|
@ -90,6 +90,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
private GroupStorageManager groupStorageManager;
|
private GroupStorageManager groupStorageManager;
|
||||||
private UserCredentialStoreManager userCredentialStorageManager;
|
private UserCredentialStoreManager userCredentialStorageManager;
|
||||||
private UserSessionProvider sessionProvider;
|
private UserSessionProvider sessionProvider;
|
||||||
|
private UserLoginFailureProvider userLoginFailureProvider;
|
||||||
private AuthenticationSessionProvider authenticationSessionProvider;
|
private AuthenticationSessionProvider authenticationSessionProvider;
|
||||||
private UserFederatedStorageProvider userFederatedStorageProvider;
|
private UserFederatedStorageProvider userFederatedStorageProvider;
|
||||||
private KeycloakContext context;
|
private KeycloakContext context;
|
||||||
|
@ -447,6 +448,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
return sessionProvider;
|
return sessionProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserLoginFailureProvider loginFailures() {
|
||||||
|
if (userLoginFailureProvider == null) {
|
||||||
|
userLoginFailureProvider = getProvider(UserLoginFailureProvider.class);
|
||||||
|
}
|
||||||
|
return userLoginFailureProvider;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationSessionProvider authenticationSessions() {
|
public AuthenticationSessionProvider authenticationSessions() {
|
||||||
if (authenticationSessionProvider == null) {
|
if (authenticationSessionProvider == null) {
|
||||||
|
|
|
@ -68,7 +68,6 @@ import org.keycloak.protocol.oidc.BackchannelLogoutResponse;
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.protocol.saml.SamlClient;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
|
@ -101,6 +100,7 @@ import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
||||||
|
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
|
||||||
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
|
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
|
||||||
import static org.keycloak.services.util.CookieHelper.getCookie;
|
import static org.keycloak.services.util.CookieHelper.getCookie;
|
||||||
|
|
||||||
|
@ -282,7 +282,11 @@ public class AuthenticationManager {
|
||||||
new UserSessionManager(session).revokeOfflineUserSession(userSession);
|
new UserSessionManager(session).revokeOfflineUserSession(userSession);
|
||||||
|
|
||||||
// Check if "online" session still exists and remove it too
|
// Check if "online" session still exists and remove it too
|
||||||
UserSessionModel onlineUserSession = session.sessions().getUserSession(realm, userSession.getId());
|
String onlineUserSessionId = userSession.getNote(CORRESPONDING_SESSION_ID);
|
||||||
|
UserSessionModel onlineUserSession = (onlineUserSessionId != null) ?
|
||||||
|
session.sessions().getUserSession(realm, onlineUserSessionId) :
|
||||||
|
session.sessions().getUserSession(realm, userSession.getId());
|
||||||
|
|
||||||
if (onlineUserSession != null) {
|
if (onlineUserSession != null) {
|
||||||
session.sessions().removeUserSession(realm, onlineUserSession);
|
session.sessions().removeUserSession(realm, onlineUserSession);
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector
|
||||||
|
|
||||||
UserLoginFailureModel userLoginFailure = getUserModel(session, event);
|
UserLoginFailureModel userLoginFailure = getUserModel(session, event);
|
||||||
if (userLoginFailure == null) {
|
if (userLoginFailure == null) {
|
||||||
userLoginFailure = session.sessions().addUserLoginFailure(realm, userId);
|
userLoginFailure = session.loginFailures().addUserLoginFailure(realm, userId);
|
||||||
}
|
}
|
||||||
userLoginFailure.setLastIPFailure(event.ip);
|
userLoginFailure.setLastIPFailure(event.ip);
|
||||||
long currentTime = Time.currentTimeMillis();
|
long currentTime = Time.currentTimeMillis();
|
||||||
|
@ -172,7 +172,7 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector
|
||||||
protected UserLoginFailureModel getUserModel(KeycloakSession session, LoginEvent event) {
|
protected UserLoginFailureModel getUserModel(KeycloakSession session, LoginEvent event) {
|
||||||
RealmModel realm = getRealmModel(session, event);
|
RealmModel realm = getRealmModel(session, event);
|
||||||
if (realm == null) return null;
|
if (realm == null) return null;
|
||||||
UserLoginFailureModel user = session.sessions().getUserLoginFailure(realm, event.userId);
|
UserLoginFailureModel user = session.loginFailures().getUserLoginFailure(realm, event.userId);
|
||||||
if (user == null) return null;
|
if (user == null) return null;
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
@ -304,7 +304,7 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTemporarilyDisabled(KeycloakSession session, RealmModel realm, UserModel user) {
|
public boolean isTemporarilyDisabled(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
UserLoginFailureModel failure = session.sessions().getUserLoginFailure(realm, user.getId());
|
UserLoginFailureModel failure = session.loginFailures().getUserLoginFailure(realm, user.getId());
|
||||||
|
|
||||||
if (failure != null) {
|
if (failure != null) {
|
||||||
int currTime = (int) (Time.currentTimeMillis() / 1000);
|
int currTime = (int) (Time.currentTimeMillis() / 1000);
|
||||||
|
|
|
@ -94,7 +94,7 @@ public class AttackDetectionResource {
|
||||||
if (!realm.isBruteForceProtected()) return data;
|
if (!realm.isBruteForceProtected()) return data;
|
||||||
|
|
||||||
|
|
||||||
UserLoginFailureModel model = session.sessions().getUserLoginFailure(realm, userId);
|
UserLoginFailureModel model = session.loginFailures().getUserLoginFailure(realm, userId);
|
||||||
if (model == null) return data;
|
if (model == null) return data;
|
||||||
|
|
||||||
boolean disabled;
|
boolean disabled;
|
||||||
|
@ -129,9 +129,9 @@ public class AttackDetectionResource {
|
||||||
} else {
|
} else {
|
||||||
auth.users().requireManage(user);
|
auth.users().requireManage(user);
|
||||||
}
|
}
|
||||||
UserLoginFailureModel model = session.sessions().getUserLoginFailure(realm, userId);
|
UserLoginFailureModel model = session.loginFailures().getUserLoginFailure(realm, userId);
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
session.sessions().removeUserLoginFailure(realm, userId);
|
session.loginFailures().removeUserLoginFailure(realm, userId);
|
||||||
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
|
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ public class AttackDetectionResource {
|
||||||
public void clearAllBruteForce() {
|
public void clearAllBruteForce() {
|
||||||
auth.users().requireManage();
|
auth.users().requireManage();
|
||||||
|
|
||||||
session.sessions().removeAllUserLoginFailures(realm);
|
session.loginFailures().removeAllUserLoginFailures(realm);
|
||||||
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
|
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -163,7 +163,7 @@ public class UserResource {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (rep.isEnabled() != null && rep.isEnabled()) {
|
if (rep.isEnabled() != null && rep.isEnabled()) {
|
||||||
UserLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, user.getId());
|
UserLoginFailureModel failureModel = session.loginFailures().getUserLoginFailure(realm, user.getId());
|
||||||
if (failureModel != null) {
|
if (failureModel != null) {
|
||||||
failureModel.clearFailures();
|
failureModel.clearFailures();
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ public abstract class AbstractShowTokensServlet extends HttpServlet {
|
||||||
return new StringBuilder("<span id=\"accessToken\">" + accessTokenPretty + "</span>")
|
return new StringBuilder("<span id=\"accessToken\">" + accessTokenPretty + "</span>")
|
||||||
.append("<span id=\"refreshToken\">" + refreshTokenPretty + "</span>")
|
.append("<span id=\"refreshToken\">" + refreshTokenPretty + "</span>")
|
||||||
.append("<span id=\"accessTokenString\">" + ctx.getTokenString() + "</span>")
|
.append("<span id=\"accessTokenString\">" + ctx.getTokenString() + "</span>")
|
||||||
|
.append("<span id=\"refreshTokenString\">" + ctx.getRefreshToken() + "</span>")
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,8 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl
|
||||||
@FindBy(id = "accessTokenString")
|
@FindBy(id = "accessTokenString")
|
||||||
private WebElement accessTokenString;
|
private WebElement accessTokenString;
|
||||||
|
|
||||||
|
@FindBy(id = "refreshTokenString")
|
||||||
|
private WebElement refreshTokenString;
|
||||||
|
|
||||||
public AccessToken getAccessToken() {
|
public AccessToken getAccessToken() {
|
||||||
try {
|
try {
|
||||||
|
@ -77,4 +79,14 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRefreshTokenString() {
|
||||||
|
try {
|
||||||
|
return refreshTokenString.getText();
|
||||||
|
} catch (NoSuchElementException nsee) {
|
||||||
|
log.warn("No refreshTokenString element found on the page");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,9 @@ public class OfflineToken extends AbstractShowTokensPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logout() {
|
public void logout() {
|
||||||
log.info("Logging out, navigating to: " + getUriBuilder().path("/logout").build().toASCIIString());
|
String uri = getUriBuilder().path("/logout").build().toASCIIString();
|
||||||
driver.navigate().to(getUriBuilder().path("/logout").build().toASCIIString());
|
log.info("Logging out, navigating to: " + uri);
|
||||||
|
driver.navigate().to(uri);
|
||||||
pause(300); // this is needed for FF for some reason
|
pause(300); // this is needed for FF for some reason
|
||||||
waitUntilElement(By.tagName("body")).is().visible();
|
waitUntilElement(By.tagName("body")).is().visible();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.Deployment;
|
import org.jboss.arquillian.container.test.api.Deployment;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
|
@ -19,6 +20,8 @@ import org.keycloak.testsuite.adapter.page.OfflineToken;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||||
|
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
|
||||||
|
import org.keycloak.testsuite.util.WaitUtils;
|
||||||
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
||||||
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
@ -66,6 +69,9 @@ public class OfflineServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
@Page
|
@Page
|
||||||
protected OAuthGrantPage oauthGrantPage;
|
protected OAuthGrantPage oauthGrantPage;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public InfinispanTestTimeServiceRule ispnTestTimeService = new InfinispanTestTimeServiceRule(this);
|
||||||
|
|
||||||
private final String DEFAULT_USERNAME = "test-user@localhost";
|
private final String DEFAULT_USERNAME = "test-user@localhost";
|
||||||
private final String DEFAULT_PASSWORD = "password";
|
private final String DEFAULT_PASSWORD = "password";
|
||||||
private final String OFFLINE_CLIENT_ID = "offline-client";
|
private final String OFFLINE_CLIENT_ID = "offline-client";
|
||||||
|
@ -94,6 +100,8 @@ public class OfflineServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
String servletUri = UriBuilder.fromUri(offlineTokenPage.toString())
|
String servletUri = UriBuilder.fromUri(offlineTokenPage.toString())
|
||||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
|
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
|
||||||
.build().toString();
|
.build().toString();
|
||||||
|
oauth.redirectUri(offlineTokenPage.toString());
|
||||||
|
oauth.clientId("offline-client");
|
||||||
|
|
||||||
driver.navigate().to(servletUri);
|
driver.navigate().to(servletUri);
|
||||||
waitUntilElement(By.tagName("body")).is().visible();
|
waitUntilElement(By.tagName("body")).is().visible();
|
||||||
|
@ -110,17 +118,31 @@ public class OfflineServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
String accessTokenId = offlineTokenPage.getAccessToken().getId();
|
String accessTokenId = offlineTokenPage.getAccessToken().getId();
|
||||||
String refreshTokenId = offlineTokenPage.getRefreshToken().getId();
|
String refreshTokenId = offlineTokenPage.getRefreshToken().getId();
|
||||||
|
|
||||||
|
// online user session will be expired and removed
|
||||||
setAdapterAndServerTimeOffset(9999);
|
setAdapterAndServerTimeOffset(9999);
|
||||||
|
|
||||||
|
// still able to access the page using the offline token
|
||||||
offlineTokenPage.navigateTo();
|
offlineTokenPage.navigateTo();
|
||||||
assertCurrentUrlStartsWith(offlineTokenPage);
|
assertCurrentUrlStartsWith(offlineTokenPage);
|
||||||
|
|
||||||
|
// assert successful refresh
|
||||||
assertThat(offlineTokenPage.getRefreshToken().getId(), not(refreshTokenId));
|
assertThat(offlineTokenPage.getRefreshToken().getId(), not(refreshTokenId));
|
||||||
assertThat(offlineTokenPage.getAccessToken().getId(), not(accessTokenId));
|
assertThat(offlineTokenPage.getAccessToken().getId(), not(accessTokenId));
|
||||||
|
|
||||||
// Ensure that logout works for webapp (even if offline token will be still valid in Keycloak DB)
|
// logout doesn't make sense because online user session is gone and there is no KEYCLOAK_IDENTITY / KEYCLOAK_SESSION cookie in the browser
|
||||||
offlineTokenPage.logout();
|
// navigate to login page which won't be possible if there's valid online session
|
||||||
assertCurrentUrlDoesntStartWith(offlineTokenPage);
|
driver.navigate().to(oauth.getLoginFormUrl());
|
||||||
|
WaitUtils.waitForPageToLoad();
|
||||||
loginPage.assertCurrent();
|
loginPage.assertCurrent();
|
||||||
|
|
||||||
|
// navigate back to offlineTokenPage to verify the offline session is still valid
|
||||||
|
offlineTokenPage.navigateTo();
|
||||||
|
assertCurrentUrlStartsWith(offlineTokenPage);
|
||||||
|
|
||||||
|
// logout the offline user session using the offline refresh token
|
||||||
|
oauth.doLogout(offlineTokenPage.getRefreshTokenString(), "secret1");
|
||||||
|
|
||||||
|
// can't access the offlineTokenPage anymore
|
||||||
offlineTokenPage.navigateTo();
|
offlineTokenPage.navigateTo();
|
||||||
assertCurrentUrlDoesntStartWith(offlineTokenPage);
|
assertCurrentUrlDoesntStartWith(offlineTokenPage);
|
||||||
loginPage.assertCurrent();
|
loginPage.assertCurrent();
|
||||||
|
|
|
@ -42,13 +42,17 @@ import org.apache.http.util.EntityUtils;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Assume;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.Config;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
import org.keycloak.admin.client.resource.ClientsResource;
|
import org.keycloak.admin.client.resource.ClientsResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
import org.keycloak.models.UserSessionSpi;
|
||||||
|
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
@ -78,8 +82,11 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
|
||||||
protected static final int CLIENTS_PER_THREAD = 30;
|
protected static final int CLIENTS_PER_THREAD = 30;
|
||||||
protected static final int DEFAULT_CLIENTS_COUNT = CLIENTS_PER_THREAD * DEFAULT_THREADS;
|
protected static final int DEFAULT_CLIENTS_COUNT = CLIENTS_PER_THREAD * DEFAULT_THREADS;
|
||||||
|
|
||||||
|
private String userSessionProvider;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void beforeTest() {
|
public void beforeTest() {
|
||||||
|
userSessionProvider = testingClient.server().fetch(session -> Config.getProvider(UserSessionSpi.NAME), String.class);
|
||||||
createClients();
|
createClients();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +112,10 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void concurrentLoginSingleUser() throws Throwable {
|
public void concurrentLoginSingleUser() throws Throwable {
|
||||||
|
Assume.assumeThat("Test runs only with InfinispanUserSessionProvider",
|
||||||
|
userSessionProvider,
|
||||||
|
Matchers.is(InfinispanUserSessionProviderFactory.PROVIDER_ID));
|
||||||
|
|
||||||
log.info("*********************************************");
|
log.info("*********************************************");
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@ -169,6 +180,10 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void concurrentLoginMultipleUsers() throws Throwable {
|
public void concurrentLoginMultipleUsers() throws Throwable {
|
||||||
|
Assume.assumeThat("Test runs only with InfinispanUserSessionProvider",
|
||||||
|
userSessionProvider,
|
||||||
|
Matchers.is(InfinispanUserSessionProviderFactory.PROVIDER_ID));
|
||||||
|
|
||||||
log.info("*********************************************");
|
log.info("*********************************************");
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
|
|
@ -265,7 +265,7 @@ public class BruteForceCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
private void addUserLoginFailure(KeycloakTestingClient testingClient) throws URISyntaxException, IOException {
|
private void addUserLoginFailure(KeycloakTestingClient testingClient) throws URISyntaxException, IOException {
|
||||||
testingClient.server().run(session -> {
|
testingClient.server().run(session -> {
|
||||||
RealmModel realm = session.realms().getRealmByName(REALM_NAME);
|
RealmModel realm = session.realms().getRealmByName(REALM_NAME);
|
||||||
UserLoginFailureModel loginFailure = session.sessions().addUserLoginFailure(realm, "login-test-1");
|
UserLoginFailureModel loginFailure = session.loginFailures().addUserLoginFailure(realm, "login-test-1");
|
||||||
loginFailure.incrementFailures();
|
loginFailure.incrementFailures();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
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.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.cache.infinispan.ClientAdapter;
|
import org.keycloak.models.cache.infinispan.ClientAdapter;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
@ -468,8 +469,11 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(offlineTokenString, "password");
|
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(offlineTokenString, "password");
|
||||||
AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
|
AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
|
||||||
|
String offlineUserSessionId = testingClient.server().fetch((KeycloakSession session) ->
|
||||||
|
session.sessions().getOfflineUserSession(session.realms().getRealmByName("test"), offlineToken.getSessionState()).getId(), String.class);
|
||||||
|
|
||||||
Assert.assertEquals(200, response.getStatusCode());
|
Assert.assertEquals(200, response.getStatusCode());
|
||||||
Assert.assertEquals(sessionId, refreshedToken.getSessionState());
|
Assert.assertEquals(offlineUserSessionId, refreshedToken.getSessionState());
|
||||||
|
|
||||||
// Assert new refreshToken in the response
|
// Assert new refreshToken in the response
|
||||||
String newRefreshToken = response.getRefreshToken();
|
String newRefreshToken = response.getRefreshToken();
|
||||||
|
|
|
@ -42,7 +42,6 @@ import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
@ -55,6 +54,7 @@ import org.keycloak.testsuite.auth.page.account.AccountManagement;
|
||||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||||
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
||||||
import org.keycloak.testsuite.util.ClientManager;
|
import org.keycloak.testsuite.util.ClientManager;
|
||||||
|
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.ServerURLs;
|
import org.keycloak.testsuite.util.ServerURLs;
|
||||||
import org.keycloak.testsuite.util.WaitUtils;
|
import org.keycloak.testsuite.util.WaitUtils;
|
||||||
|
@ -68,6 +68,9 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
@Rule
|
@Rule
|
||||||
public AssertEvents events = new AssertEvents(this);
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public InfinispanTestTimeServiceRule ispnTestTimeService = new InfinispanTestTimeServiceRule(this);
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
protected AppPage appPage;
|
protected AppPage appPage;
|
||||||
|
|
||||||
|
@ -190,9 +193,8 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||||
String idTokenString = tokenResponse.getIdToken();
|
String idTokenString = tokenResponse.getIdToken();
|
||||||
|
|
||||||
// wait for a timeout
|
// expire online user session
|
||||||
// setTimeOffset doesn't work because session cookie is not invalidated thus the logout flow would continue with browser logout
|
setTimeOffset(9999);
|
||||||
TimeUnit.SECONDS.sleep(3);
|
|
||||||
|
|
||||||
String logoutUrl = oauth.getLogoutUrl().redirectUri(oauth.APP_AUTH_ROOT).idTokenHint(idTokenString).build();
|
String logoutUrl = oauth.getLogoutUrl().redirectUri(oauth.APP_AUTH_ROOT).idTokenHint(idTokenString).build();
|
||||||
driver.navigate().to(logoutUrl);
|
driver.navigate().to(logoutUrl);
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.keycloak.models.cache.infinispan.RealmAdapter;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
@ -111,7 +112,7 @@ public class CacheTest extends AbstractTestRealmKeycloakTest {
|
||||||
user.setFirstName("firstName");
|
user.setFirstName("firstName");
|
||||||
user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
|
user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().createUserSession("123", realm, user, "testAddUserNotAddedToCache",
|
UserSessionModel userSession = session.sessions().createUserSession(UUID.randomUUID().toString(), realm, user, "testAddUserNotAddedToCache",
|
||||||
"127.0.0.1", "auth", false, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
"127.0.0.1", "auth", false, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||||
user = userSession.getUser();
|
user = userSession.getUser();
|
||||||
|
|
||||||
|
|
|
@ -1,278 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 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.After;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.UserManager;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.models.UserSessionModel;
|
|
||||||
import org.keycloak.models.UserSessionProvider;
|
|
||||||
import org.keycloak.models.UserSessionProviderFactory;
|
|
||||||
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProvider;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
|
||||||
import org.keycloak.services.managers.UserSessionManager;
|
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
* @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a>
|
|
||||||
*/
|
|
||||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
|
||||||
public class UserSessionInitializerTest extends AbstractTestRealmKeycloakTest {
|
|
||||||
private final String realmName = "test";
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void before() {
|
|
||||||
testingClient.server().run(session -> {
|
|
||||||
RealmModel realm = session.realms().getRealm("test");
|
|
||||||
session.users().addUser(realm, "user1").setEmail("user1@localhost");
|
|
||||||
session.users().addUser(realm, "user2").setEmail("user2@localhost");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void after() {
|
|
||||||
testingClient.server().run(session -> {
|
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
|
||||||
session.sessions().removeUserSessions(realm);
|
|
||||||
|
|
||||||
UserModel user1 = session.users().getUserByUsername(realm, "user1");
|
|
||||||
UserModel user2 = session.users().getUserByUsername(realm, "user2");
|
|
||||||
|
|
||||||
UserManager um = new UserManager(session);
|
|
||||||
if (user1 != null)
|
|
||||||
um.removeUser(realm, user1);
|
|
||||||
if (user2 != null)
|
|
||||||
um.removeUser(realm, user2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testUserSessionInitializer(KeycloakSession session) {
|
|
||||||
AtomicReference<Integer> startedAtomic = new AtomicReference<>();
|
|
||||||
AtomicReference<UserSessionModel[]> origSessionsAtomic = new AtomicReference<>();
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession SessionInit1) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, SessionInit1);
|
|
||||||
|
|
||||||
int started = Time.currentTime();
|
|
||||||
startedAtomic.set(started);
|
|
||||||
|
|
||||||
UserSessionModel[] origSessions = createSessionsInPersisterOnly(currentSession);
|
|
||||||
origSessionsAtomic.set(origSessions);
|
|
||||||
|
|
||||||
// Load sessions from persister into infinispan/memory
|
|
||||||
UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) currentSession.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
|
|
||||||
userSessionFactory.loadPersistentSessions(currentSession.getKeycloakSessionFactory(), 1, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession SessionInit2) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, SessionInit2);
|
|
||||||
RealmModel realm = currentSession.realms().getRealmByName(realmName);
|
|
||||||
|
|
||||||
int started = startedAtomic.get();
|
|
||||||
|
|
||||||
UserSessionModel[] origSessions = origSessionsAtomic.get();
|
|
||||||
|
|
||||||
// Assert sessions are in
|
|
||||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
|
||||||
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
|
||||||
|
|
||||||
assertThat("Count of offline sesions for client 'test-app'", currentSession.sessions().getOfflineSessionsCount(realm, testApp), is((long) 3));
|
|
||||||
assertThat("Count of offline sesions for client 'third-party'", currentSession.sessions().getOfflineSessionsCount(realm, thirdparty), is((long) 1));
|
|
||||||
|
|
||||||
List<UserSessionModel> loadedSessions = currentSession.sessions().getOfflineUserSessionsStream(realm, testApp, 0, 10)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
|
|
||||||
|
|
||||||
assertSessionLoaded(loadedSessions, origSessions[0].getId(), currentSession.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "test-app", "third-party");
|
|
||||||
assertSessionLoaded(loadedSessions, origSessions[1].getId(), currentSession.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, started, "test-app");
|
|
||||||
assertSessionLoaded(loadedSessions, origSessions[2].getId(), currentSession.users().getUserByUsername(realm, "user2"), "127.0.0.3", started, started, "test-app");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testUserSessionInitializerWithDeletingClient(KeycloakSession session) {
|
|
||||||
AtomicReference<Integer> startedAtomic = new AtomicReference<>();
|
|
||||||
AtomicReference<UserSessionModel[]> origSessionsAtomic = new AtomicReference<>();
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession SessionInitWithDeleting1) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, SessionInitWithDeleting1);
|
|
||||||
|
|
||||||
RealmModel realm = currentSession.realms().getRealmByName(realmName);
|
|
||||||
|
|
||||||
int started = Time.currentTime();
|
|
||||||
startedAtomic.set(started);
|
|
||||||
|
|
||||||
origSessionsAtomic.set(createSessionsInPersisterOnly(currentSession));
|
|
||||||
|
|
||||||
// Delete one of the clients now. Delete it directly in DB just for the purpose of simulating the issue (normally clients should be removed through ClientManager)
|
|
||||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
|
||||||
realm.removeClient(testApp.getId());
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession SessionInitWithDeleting2) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, SessionInitWithDeleting2);
|
|
||||||
|
|
||||||
// Load sessions from persister into infinispan/memory
|
|
||||||
UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) currentSession.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
|
|
||||||
userSessionFactory.loadPersistentSessions(currentSession.getKeycloakSessionFactory(), 1, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession SessionInitWithDeleting3) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, SessionInitWithDeleting3);
|
|
||||||
RealmModel realm = currentSession.realms().getRealmByName(realmName);
|
|
||||||
|
|
||||||
int started = startedAtomic.get();
|
|
||||||
|
|
||||||
UserSessionModel[] origSessions = origSessionsAtomic.get();
|
|
||||||
|
|
||||||
// Assert sessions are in
|
|
||||||
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
|
||||||
|
|
||||||
assertThat("Count of offline sesions for client 'third-party'", currentSession.sessions().getOfflineSessionsCount(realm, thirdparty), is((long) 1));
|
|
||||||
List<UserSessionModel> loadedSessions = currentSession.sessions().getOfflineUserSessionsStream(realm, thirdparty, 0, 10)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
assertThat("Size of loaded Sessions", loadedSessions.size(), is(1));
|
|
||||||
assertSessionLoaded(loadedSessions, origSessions[0].getId(), currentSession.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "third-party");
|
|
||||||
|
|
||||||
// Revert client
|
|
||||||
realm.addClient("test-app");
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create sessions in persister + infinispan, but then delete them from infinispan cache. This is to allow later testing of initializer. Return the list of "origSessions"
|
|
||||||
private UserSessionModel[] createSessionsInPersisterOnly(KeycloakSession session) {
|
|
||||||
AtomicReference<UserSessionModel[]> origSessionsAtomic = new AtomicReference<>();
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession createSessionPersister1) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, createSessionPersister1);
|
|
||||||
|
|
||||||
UserSessionModel[] origSessions = createSessions(currentSession);
|
|
||||||
origSessionsAtomic.set(origSessions);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession createSessionPersister2) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, createSessionPersister2);
|
|
||||||
RealmModel realm = currentSession.realms().getRealmByName(realmName);
|
|
||||||
UserSessionManager sessionManager = new UserSessionManager(currentSession);
|
|
||||||
|
|
||||||
UserSessionModel[] origSessions = origSessionsAtomic.get();
|
|
||||||
|
|
||||||
for (UserSessionModel origSession : origSessions) {
|
|
||||||
UserSessionModel userSession = currentSession.sessions().getUserSession(realm, origSession.getId());
|
|
||||||
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
|
|
||||||
sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession createSessionPersister3) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, createSessionPersister3);
|
|
||||||
RealmModel realm = currentSession.realms().getRealmByName(realmName);
|
|
||||||
|
|
||||||
// Delete local user cache (persisted sessions are still kept)
|
|
||||||
InfinispanUserSessionProvider userSessionProvider = (InfinispanUserSessionProvider) currentSession.getProvider(UserSessionProvider.class);
|
|
||||||
userSessionProvider.removeLocalUserSessions(realm.getId(), true);
|
|
||||||
|
|
||||||
// Clear ispn cache to ensure initializerState is removed as well
|
|
||||||
InfinispanConnectionProvider infinispan = currentSession.getProvider(InfinispanConnectionProvider.class);
|
|
||||||
infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession createSessionPersister4) -> {
|
|
||||||
KeycloakSession currentSession = inheritClientConnection(session, createSessionPersister4);
|
|
||||||
RealmModel realm = currentSession.realms().getRealmByName(realmName);
|
|
||||||
|
|
||||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
|
||||||
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
|
||||||
assertThat("Count of offline sessions for client 'test-app'", currentSession.sessions().getOfflineSessionsCount(realm, testApp), is((long) 0));
|
|
||||||
assertThat("Count of offline sessions for client 'third-party'", currentSession.sessions().getOfflineSessionsCount(realm, thirdparty), is((long) 0));
|
|
||||||
});
|
|
||||||
|
|
||||||
return origSessionsAtomic.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private AuthenticatedClientSessionModel createClientSession(KeycloakSession session, ClientModel client, UserSessionModel userSession, String redirect, String state) {
|
|
||||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
|
||||||
|
|
||||||
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
|
|
||||||
clientSession.setRedirectUri(redirect);
|
|
||||||
if (state != null)
|
|
||||||
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
|
|
||||||
return clientSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserSessionModel[] createSessions(KeycloakSession session) {
|
|
||||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
|
||||||
|
|
||||||
UserSessionModel[] sessions = new UserSessionModel[3];
|
|
||||||
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null);
|
|
||||||
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state");
|
|
||||||
createClientSession(session, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state");
|
|
||||||
|
|
||||||
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null);
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state");
|
|
||||||
|
|
||||||
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user2"), "user2", "127.0.0.3", "form", true, null, null);
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state");
|
|
||||||
|
|
||||||
return sessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
|
|
||||||
for (UserSessionModel session : sessions) {
|
|
||||||
if (session.getId().equals(id)) {
|
|
||||||
UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert.fail("Session with ID " + id + " not found in the list");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,613 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 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.After;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.UserManager;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.models.UserSessionModel;
|
|
||||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
|
||||||
import org.keycloak.services.managers.ClientManager;
|
|
||||||
import org.keycloak.services.managers.RealmManager;
|
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import org.keycloak.models.Constants;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
|
||||||
public class UserSessionPersisterProviderTest extends AbstractTestRealmKeycloakTest {
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void before() {
|
|
||||||
testingClient.server().run(session -> {
|
|
||||||
initStuff(session);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initStuff(KeycloakSession session) {
|
|
||||||
RealmModel realm = session.realms().getRealm("test");
|
|
||||||
session.users().addUser(realm, "user1").setEmail("user1@localhost");
|
|
||||||
session.users().addUser(realm, "user2").setEmail("user2@localhost");
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void after() {
|
|
||||||
testingClient.server().run(session -> {
|
|
||||||
RealmModel realm = session.realms().getRealm("test");
|
|
||||||
session.sessions().removeUserSessions(realm);
|
|
||||||
UserModel user1 = session.users().getUserByUsername(realm, "user1");
|
|
||||||
UserModel user2 = session.users().getUserByUsername(realm, "user2");
|
|
||||||
|
|
||||||
UserManager um = new UserManager(session);
|
|
||||||
if (user1 != null) {
|
|
||||||
um.removeUser(realm, user1);
|
|
||||||
}
|
|
||||||
if (user2 != null) {
|
|
||||||
um.removeUser(realm, user2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testPersistenceWithLoad(KeycloakSession session) {
|
|
||||||
int started = Time.currentTime();
|
|
||||||
UserSessionModel[][] origSessions = new UserSessionModel[1][1];
|
|
||||||
final UserSessionModel[] userSession = new UserSessionModel[1];
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL) -> {
|
|
||||||
// Create some sessions in infinispan
|
|
||||||
origSessions[0] = createSessions(sessionWL);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL22) -> {
|
|
||||||
// Persist 3 created userSessions and clientSessions as offline
|
|
||||||
RealmModel realm = sessionWL22.realms().getRealm("test");
|
|
||||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
|
||||||
sessionWL22.sessions().getUserSessionsStream(realm, testApp).collect(Collectors.toList())
|
|
||||||
.forEach(userSessionLooper -> persistUserSession(sessionWL22, userSessionLooper, true));
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL2) -> {
|
|
||||||
// Persist 1 online session
|
|
||||||
RealmModel realm = sessionWL2.realms().getRealm("test");
|
|
||||||
userSession[0] = sessionWL2.sessions().getUserSession(realm, origSessions[0][0].getId());
|
|
||||||
persistUserSession(sessionWL2, userSession[0], false);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL3) -> {// Assert online session
|
|
||||||
RealmModel realm = sessionWL3.realms().getRealm("test");
|
|
||||||
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(sessionWL3, false, 1, 1, 1);
|
|
||||||
UserSessionProviderTest.assertSession(loadedSessions.get(0), sessionWL3.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "test-app", "third-party");
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL4) -> {
|
|
||||||
// Assert offline sessions
|
|
||||||
RealmModel realm = sessionWL4.realms().getRealm("test");
|
|
||||||
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(sessionWL4, true, 2, 2, 3);
|
|
||||||
UserSessionProviderTest.assertSessions(loadedSessions, origSessions[0]);
|
|
||||||
|
|
||||||
assertSessionLoaded(loadedSessions, origSessions[0][0].getId(), sessionWL4.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "test-app", "third-party");
|
|
||||||
assertSessionLoaded(loadedSessions, origSessions[0][1].getId(), sessionWL4.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, started, "test-app");
|
|
||||||
assertSessionLoaded(loadedSessions, origSessions[0][2].getId(), sessionWL4.users().getUserByUsername(realm, "user2"), "127.0.0.3", started, started, "test-app");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testUpdateAndRemove(KeycloakSession session) {
|
|
||||||
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<>();
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove1) -> {
|
|
||||||
KeycloakSession currentSession = sesUpdateRemove1;
|
|
||||||
|
|
||||||
// Create some sessions in infinispan
|
|
||||||
UserSessionModel[] origSessions = createSessions(currentSession);
|
|
||||||
origSessionsAt.set(origSessions);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove2) -> {
|
|
||||||
KeycloakSession currentSession = sesUpdateRemove2;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
UserSessionModel[] origSessions = origSessionsAt.get();
|
|
||||||
|
|
||||||
// Persist 1 offline session
|
|
||||||
UserSessionModel userSession = currentSession.sessions().getUserSession(realm, origSessions[1].getId());
|
|
||||||
userSessionAt.set(userSession);
|
|
||||||
|
|
||||||
persistUserSession(currentSession, userSession, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove3) -> {
|
|
||||||
KeycloakSession currentSession = sesUpdateRemove3;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
UserSessionPersisterProvider persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
// Load offline session
|
|
||||||
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
|
|
||||||
loadedSessionsAt.set(loadedSessions);
|
|
||||||
|
|
||||||
UserSessionModel persistedSession = loadedSessions.get(0);
|
|
||||||
persistedSessionAt.set(persistedSession);
|
|
||||||
|
|
||||||
UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, started, "test-app");
|
|
||||||
|
|
||||||
// create new clientSession
|
|
||||||
AuthenticatedClientSessionModel clientSession = createClientSession(currentSession, realm.getClientByClientId("third-party"), currentSession.sessions().getUserSession(realm, persistedSession.getId()),
|
|
||||||
"http://redirect", "state");
|
|
||||||
persister.createClientSession(clientSession, true);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove4) -> {
|
|
||||||
KeycloakSession currentSession = sesUpdateRemove4;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
UserSessionPersisterProvider persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
UserSessionModel userSession = userSessionAt.get();
|
|
||||||
|
|
||||||
// Remove clientSession
|
|
||||||
persister.removeClientSession(userSession.getId(), realm.getClientByClientId("third-party").getId(), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove5) -> {
|
|
||||||
KeycloakSession currentSession = sesUpdateRemove5;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
UserSessionPersisterProvider persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
List<UserSessionModel> loadedSessions = loadedSessionsAt.get();
|
|
||||||
UserSessionModel persistedSession = persistedSessionAt.get();
|
|
||||||
|
|
||||||
// Assert clientSession removed
|
|
||||||
loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
|
|
||||||
persistedSession = loadedSessions.get(0);
|
|
||||||
UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, started, "test-app");
|
|
||||||
|
|
||||||
// Remove userSession
|
|
||||||
persister.removeUserSession(persistedSession.getId(), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove6) -> {
|
|
||||||
KeycloakSession currentSession = sesUpdateRemove6;
|
|
||||||
// Assert nothing found
|
|
||||||
loadPersistedSessionsPaginated(currentSession, true, 10, 0, 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testOnRealmRemoved(KeycloakSession session) {
|
|
||||||
AtomicReference<String> userSessionID = new AtomicReference<>();
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR1) -> {
|
|
||||||
KeycloakSession currentSession = sessionRR1;
|
|
||||||
RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
|
|
||||||
fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName()));
|
|
||||||
|
|
||||||
fooRealm.addClient("foo-app");
|
|
||||||
currentSession.users().addUser(fooRealm, "user3");
|
|
||||||
|
|
||||||
UserSessionModel userSession = currentSession.sessions().createUserSession(fooRealm, currentSession.users().getUserByUsername(fooRealm, "user3"), "user3", "127.0.0.1", "form", true, null, null);
|
|
||||||
userSessionID.set(userSession.getId());
|
|
||||||
|
|
||||||
createClientSession(currentSession, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR2) -> {
|
|
||||||
KeycloakSession currentSession = sessionRR2;
|
|
||||||
|
|
||||||
// Persist offline session
|
|
||||||
RealmModel fooRealm = currentSession.realms().getRealm("foo");
|
|
||||||
UserSessionModel userSession = currentSession.sessions().getUserSession(fooRealm, userSessionID.get());
|
|
||||||
persistUserSession(currentSession, userSession, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR3) -> {
|
|
||||||
KeycloakSession currentSession = sessionRR3;
|
|
||||||
|
|
||||||
// Assert session was persisted
|
|
||||||
loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
|
|
||||||
|
|
||||||
// Remove realm
|
|
||||||
RealmManager realmMgr = new RealmManager(currentSession);
|
|
||||||
realmMgr.removeRealm(realmMgr.getRealm("foo"));
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR4) -> {
|
|
||||||
KeycloakSession currentSession = sessionRR4;
|
|
||||||
|
|
||||||
// Assert nothing loaded
|
|
||||||
loadPersistedSessionsPaginated(currentSession, true, 10, 0, 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testOnClientRemoved(KeycloakSession session) {
|
|
||||||
int started = Time.currentTime();
|
|
||||||
AtomicReference<String> userSessionID = new AtomicReference<>();
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR1) -> {
|
|
||||||
KeycloakSession currentSession = sessionCR1;
|
|
||||||
RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
|
|
||||||
fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX));
|
|
||||||
|
|
||||||
fooRealm.addClient("foo-app");
|
|
||||||
fooRealm.addClient("bar-app");
|
|
||||||
currentSession.users().addUser(fooRealm, "user3");
|
|
||||||
|
|
||||||
UserSessionModel userSession = currentSession.sessions().createUserSession(fooRealm, currentSession.users().getUserByUsername(fooRealm, "user3"), "user3", "127.0.0.1", "form", true, null, null);
|
|
||||||
userSessionID.set(userSession.getId());
|
|
||||||
|
|
||||||
createClientSession(currentSession, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
|
|
||||||
createClientSession(currentSession, fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state");
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR2) -> {
|
|
||||||
KeycloakSession currentSession = sessionCR2;
|
|
||||||
RealmModel fooRealm = currentSession.realms().getRealm("foo");
|
|
||||||
|
|
||||||
// Persist offline session
|
|
||||||
UserSessionModel userSession = currentSession.sessions().getUserSession(fooRealm, userSessionID.get());
|
|
||||||
persistUserSession(currentSession, userSession, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR3) -> {
|
|
||||||
KeycloakSession currentSession = sessionCR3;
|
|
||||||
|
|
||||||
RealmManager realmMgr = new RealmManager(currentSession);
|
|
||||||
ClientManager clientMgr = new ClientManager(realmMgr);
|
|
||||||
RealmModel fooRealm = realmMgr.getRealm("foo");
|
|
||||||
|
|
||||||
// Assert session was persisted with both clientSessions
|
|
||||||
UserSessionModel persistedSession = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1).get(0);
|
|
||||||
UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername(fooRealm, "user3"), "127.0.0.1", started, started, "foo-app", "bar-app");
|
|
||||||
|
|
||||||
// Remove foo-app client
|
|
||||||
ClientModel client = fooRealm.getClientByClientId("foo-app");
|
|
||||||
clientMgr.removeClient(fooRealm, client);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR4) -> {
|
|
||||||
KeycloakSession currentSession = sessionCR4;
|
|
||||||
RealmManager realmMgr = new RealmManager(currentSession);
|
|
||||||
ClientManager clientMgr = new ClientManager(realmMgr);
|
|
||||||
RealmModel fooRealm = realmMgr.getRealm("foo");
|
|
||||||
|
|
||||||
// Assert just one bar-app clientSession persisted now
|
|
||||||
UserSessionModel persistedSession = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1).get(0);
|
|
||||||
UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername(fooRealm, "user3"), "127.0.0.1", started, started, "bar-app");
|
|
||||||
|
|
||||||
// Remove bar-app client
|
|
||||||
ClientModel client = fooRealm.getClientByClientId("bar-app");
|
|
||||||
clientMgr.removeClient(fooRealm, client);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR5) -> {
|
|
||||||
KeycloakSession currentSession = sessionCR5;
|
|
||||||
|
|
||||||
// Assert loading still works - last userSession is still there, but no clientSession on it
|
|
||||||
loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
RealmManager realmMgr = new RealmManager(currentSession);
|
|
||||||
realmMgr.removeRealm(realmMgr.getRealm("foo"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testOnUserRemoved(KeycloakSession session) {
|
|
||||||
int started = Time.currentTime();
|
|
||||||
AtomicReference<UserSessionModel[]> origSessionsAt = new AtomicReference<>();
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionOR1) -> {
|
|
||||||
KeycloakSession currentSession = sessionOR1;
|
|
||||||
|
|
||||||
// Create some sessions in infinispan
|
|
||||||
UserSessionModel[] origSessions = createSessions(currentSession);
|
|
||||||
origSessionsAt.set(origSessions);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionOR2) -> {
|
|
||||||
KeycloakSession currentSession = sessionOR2;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
UserSessionModel[] origSessions = origSessionsAt.get();
|
|
||||||
|
|
||||||
// Persist 2 offline sessions of 2 users
|
|
||||||
UserSessionModel userSession1 = currentSession.sessions().getUserSession(realm, origSessions[1].getId());
|
|
||||||
UserSessionModel userSession2 = currentSession.sessions().getUserSession(realm, origSessions[2].getId());
|
|
||||||
persistUserSession(currentSession, userSession1, true);
|
|
||||||
persistUserSession(currentSession, userSession2, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionOR3) -> {
|
|
||||||
KeycloakSession currentSession = sessionOR3;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
// Load offline sessions
|
|
||||||
loadPersistedSessionsPaginated(currentSession, true, 10, 1, 2);
|
|
||||||
|
|
||||||
// Properly delete user and assert his offlineSession removed
|
|
||||||
UserModel user1 = currentSession.users().getUserByUsername(realm, "user1");
|
|
||||||
new UserManager(currentSession).removeUser(realm, user1);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionOR4) -> {
|
|
||||||
KeycloakSession currentSession = sessionOR4;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
UserSessionPersisterProvider persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
Assert.assertEquals(1, persister.getUserSessionsCount(true));
|
|
||||||
|
|
||||||
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
|
|
||||||
UserSessionModel persistedSession = loadedSessions.get(0);
|
|
||||||
UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername(realm, "user2"), "127.0.0.3", started, started, "test-app");
|
|
||||||
|
|
||||||
// KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly".
|
|
||||||
// No exception will happen. However session will be still there
|
|
||||||
UserModel user2 = currentSession.users().getUserByUsername(realm, "user2");
|
|
||||||
currentSession.users().removeUser(realm, user2);
|
|
||||||
|
|
||||||
loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
UserSessionModel userSession = loadedSessions.get(0);
|
|
||||||
currentSession.sessions().removeUserSession(realm, userSession);
|
|
||||||
persister.removeUserSession(userSession.getId(), userSession.isOffline());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// KEYCLOAK-1999
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testNoSessions(KeycloakSession session) {
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionNS) -> {
|
|
||||||
UserSessionPersisterProvider persister = sessionNS.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
Stream<UserSessionModel> sessions = persister.loadUserSessionsStream(0, 1, true, 0, "abc");
|
|
||||||
Assert.assertEquals(0, sessions.count());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testMoreSessions(KeycloakSession session) {
|
|
||||||
AtomicReference<List<UserSessionModel>> userSessionsAt = new AtomicReference<>();
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionMS1) -> {
|
|
||||||
KeycloakSession currentSession = sessionMS1;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
// Create 10 userSessions - each having 1 clientSession
|
|
||||||
List<UserSessionModel> userSessions = new ArrayList<>();
|
|
||||||
UserModel user = currentSession.users().getUserByUsername(realm, "user1");
|
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++) {
|
|
||||||
// Having different offsets for each session (to ensure that lastSessionRefresh is also different)
|
|
||||||
Time.setOffset(i);
|
|
||||||
|
|
||||||
UserSessionModel userSession = currentSession.sessions().createUserSession(realm, user, "user1", "127.0.0.1", "form", true, null, null);
|
|
||||||
createClientSession(currentSession, realm.getClientByClientId("test-app"), userSession, "http://redirect", "state");
|
|
||||||
userSessions.add(userSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
userSessionsAt.set(userSessions);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionMS2) -> {
|
|
||||||
KeycloakSession currentSession = sessionMS2;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
List<UserSessionModel> userSessions = userSessionsAt.get();
|
|
||||||
|
|
||||||
for (UserSessionModel userSession : userSessions) {
|
|
||||||
UserSessionModel userSession2 = currentSession.sessions().getUserSession(realm, userSession.getId());
|
|
||||||
persistUserSession(currentSession, userSession2, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionMS3) -> {
|
|
||||||
KeycloakSession currentSession = sessionMS3;
|
|
||||||
RealmModel realm = currentSession.realms().getRealm("test");
|
|
||||||
|
|
||||||
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 2, 10, 20);
|
|
||||||
UserModel user = currentSession.users().getUserByUsername(realm, "user1");
|
|
||||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
|
||||||
|
|
||||||
for (UserSessionModel loadedSession : loadedSessions) {
|
|
||||||
assertEquals(user.getId(), loadedSession.getUser().getId());
|
|
||||||
assertEquals("127.0.0.1", loadedSession.getIpAddress());
|
|
||||||
assertEquals(user.getUsername(), loadedSession.getLoginUsername());
|
|
||||||
|
|
||||||
assertEquals(1, loadedSession.getAuthenticatedClientSessions().size());
|
|
||||||
assertTrue(loadedSession.getAuthenticatedClientSessions().containsKey(testApp.getId()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testExpiredSessions(KeycloakSession session) {
|
|
||||||
UserSessionModel[][] origSessions = {new UserSessionModel[1]};
|
|
||||||
int started = Time.currentTime();
|
|
||||||
final UserSessionModel[] userSession1 = {null};
|
|
||||||
final UserSessionModel[] userSession2 = {null};
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionES) -> {
|
|
||||||
// Create some sessions in infinispan
|
|
||||||
UserSessionPersisterProvider persister = sessionES.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
origSessions[0] = createSessions(sessionES);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionES2) -> {
|
|
||||||
// Persist 2 offline sessions of 2 users
|
|
||||||
RealmModel realm = sessionES2.realms().getRealm("test");
|
|
||||||
UserSessionPersisterProvider persister = sessionES2.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
userSession1[0] = sessionES2.sessions().getUserSession(realm, origSessions[0][1].getId());
|
|
||||||
userSession2[0] = sessionES2.sessions().getUserSession(realm, origSessions[0][2].getId());
|
|
||||||
persistUserSession(sessionES2, userSession1[0], true);
|
|
||||||
persistUserSession(sessionES2, userSession2[0], true);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionES3) -> {
|
|
||||||
// Update one of the sessions with lastSessionRefresh of 20 days ahead
|
|
||||||
int lastSessionRefresh = Time.currentTime() + 1728000;
|
|
||||||
RealmModel realm = sessionES3.realms().getRealm("test");
|
|
||||||
UserSessionPersisterProvider persister = sessionES3.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
persister.updateLastSessionRefreshes(realm, lastSessionRefresh, Collections.singleton(userSession1[0].getId()), true);
|
|
||||||
|
|
||||||
// Increase time offset - 40 days
|
|
||||||
Time.setOffset(3456000);
|
|
||||||
try {
|
|
||||||
// Run expiration thread
|
|
||||||
persister.removeExpired(realm);
|
|
||||||
|
|
||||||
// Test the updated session is still in persister. Not updated session is not there anymore
|
|
||||||
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(sessionES3, true, 10, 1, 1);
|
|
||||||
UserSessionModel persistedSession = loadedSessions.get(0);
|
|
||||||
UserSessionProviderTest.assertSession(persistedSession, sessionES3.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, lastSessionRefresh, "test-app");
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// Cleanup
|
|
||||||
Time.setOffset(0);
|
|
||||||
session.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private AuthenticatedClientSessionModel createClientSession(KeycloakSession session, ClientModel client, UserSessionModel userSession, String redirect, String state) {
|
|
||||||
RealmModel realm = session.realms().getRealm("test");
|
|
||||||
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
|
|
||||||
clientSession.setRedirectUri(redirect);
|
|
||||||
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
|
|
||||||
return clientSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserSessionModel[] createSessions(KeycloakSession session) {
|
|
||||||
RealmModel realm = session.realms().getRealm("test");
|
|
||||||
UserSessionModel[] sessions = new UserSessionModel[3];
|
|
||||||
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null);
|
|
||||||
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state");
|
|
||||||
createClientSession(session, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state");
|
|
||||||
|
|
||||||
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null);
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state");
|
|
||||||
|
|
||||||
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user2"), "user2", "127.0.0.3", "form", true, null, null);
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state");
|
|
||||||
|
|
||||||
return sessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void persistUserSession(KeycloakSession session, UserSessionModel userSession, boolean offline) {
|
|
||||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
persister.createUserSession(userSession, offline);
|
|
||||||
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
|
|
||||||
persister.createClientSession(clientSession, offline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
|
|
||||||
for (UserSessionModel session : sessions) {
|
|
||||||
if (session.getId().equals(id)) {
|
|
||||||
UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert.fail("Session with ID " + id + " not found in the list");
|
|
||||||
}
|
|
||||||
|
|
||||||
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<>();
|
|
||||||
int lastCreatedOn = 0;
|
|
||||||
String lastSessionId = "abc";
|
|
||||||
|
|
||||||
while (next) {
|
|
||||||
List<UserSessionModel> sess = persister
|
|
||||||
.loadUserSessionsStream(0, sessionsPerPage, offline, lastCreatedOn, lastSessionId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (sess.size() < sessionsPerPage) {
|
|
||||||
next = false;
|
|
||||||
|
|
||||||
// We had at least some session
|
|
||||||
if (sess.size() > 0) {
|
|
||||||
pageCount++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pageCount++;
|
|
||||||
|
|
||||||
UserSessionModel lastSession = sess.get(sess.size() - 1);
|
|
||||||
lastCreatedOn = lastSession.getStarted();
|
|
||||||
lastSessionId = lastSession.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.addAll(sess);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertEquals(expectedPageCount, pageCount);
|
|
||||||
Assert.assertEquals(expectedSessionsCount, result.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,7 +20,6 @@ package org.keycloak.testsuite.model;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
@ -30,10 +29,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserManager;
|
import org.keycloak.models.UserManager;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
|
||||||
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStoreFactory;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.managers.ClientManager;
|
import org.keycloak.services.managers.ClientManager;
|
||||||
|
@ -42,8 +38,6 @@ import org.keycloak.services.managers.UserSessionManager;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
||||||
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
|
|
||||||
import org.keycloak.timer.TimerProvider;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -64,20 +58,15 @@ import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.A
|
||||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||||
public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTest {
|
public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
@Rule
|
|
||||||
public InfinispanTestTimeServiceRule ispnTestTimeService = new InfinispanTestTimeServiceRule(this);
|
|
||||||
|
|
||||||
private static KeycloakSession currentSession;
|
private static KeycloakSession currentSession;
|
||||||
private static RealmModel realm;
|
private static RealmModel realm;
|
||||||
private static UserSessionManager sessionManager;
|
private static UserSessionManager sessionManager;
|
||||||
private static UserSessionPersisterProvider persister;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
testingClient.server().run(session -> {
|
testingClient.server().run(session -> {
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionBefore) -> {
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionBefore) -> {
|
||||||
reloadState(sessionBefore, true);
|
reloadState(sessionBefore, true);
|
||||||
persister = sessionBefore.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -115,7 +104,6 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
currentSession = sessionCrud2;
|
currentSession = sessionCrud2;
|
||||||
realm = currentSession.realms().getRealm("test");
|
realm = currentSession.realms().getRealm("test");
|
||||||
sessionManager = new UserSessionManager(currentSession);
|
sessionManager = new UserSessionManager(currentSession);
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
// Key is userSession ID, values are client UUIDS
|
// Key is userSession ID, values are client UUIDS
|
||||||
// Persist 3 created userSessions and clientSessions as offline
|
// Persist 3 created userSessions and clientSessions as offline
|
||||||
|
@ -128,7 +116,6 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
currentSession = sessionCrud3;
|
currentSession = sessionCrud3;
|
||||||
realm = currentSession.realms().getRealm("test");
|
realm = currentSession.realms().getRealm("test");
|
||||||
sessionManager = new UserSessionManager(currentSession);
|
sessionManager = new UserSessionManager(currentSession);
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
// Assert all previously saved offline sessions found
|
// Assert all previously saved offline sessions found
|
||||||
for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
|
for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
|
||||||
|
@ -165,17 +152,10 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
currentSession = sessionCrud4;
|
currentSession = sessionCrud4;
|
||||||
realm = currentSession.realms().getRealm("test");
|
realm = currentSession.realms().getRealm("test");
|
||||||
sessionManager = new UserSessionManager(currentSession);
|
sessionManager = new UserSessionManager(currentSession);
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
// Assert userSession revoked
|
// Assert userSession revoked
|
||||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
|
||||||
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
||||||
|
|
||||||
// Still 2 sessions. The count of sessions by client may not be accurate after revoke due the
|
|
||||||
// performance optimizations (the "127.0.0.1" currentSession still has another client "thirdparty" in it)
|
|
||||||
Assert.assertEquals(2, currentSession.sessions().getOfflineSessionsCount(realm, testApp));
|
|
||||||
Assert.assertEquals(1, currentSession.sessions().getOfflineSessionsCount(realm, thirdparty));
|
|
||||||
|
|
||||||
List<UserSessionModel> thirdpartySessions = currentSession.sessions().getOfflineUserSessionsStream(realm, thirdparty, 0, 10)
|
List<UserSessionModel> thirdpartySessions = currentSession.sessions().getOfflineUserSessionsStream(realm, thirdparty, 0, 10)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
Assert.assertEquals(1, thirdpartySessions.size());
|
Assert.assertEquals(1, thirdpartySessions.size());
|
||||||
|
@ -201,7 +181,6 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
currentSession = sessionCrud5;
|
currentSession = sessionCrud5;
|
||||||
realm = currentSession.realms().getRealm("test");
|
realm = currentSession.realms().getRealm("test");
|
||||||
sessionManager = new UserSessionManager(currentSession);
|
sessionManager = new UserSessionManager(currentSession);
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
ClientModel testApp = realm.getClientByClientId("test-app");
|
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||||
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
||||||
|
@ -231,9 +210,12 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR1) -> {
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR1) -> {
|
||||||
currentSession = sessionRR1;
|
currentSession = sessionRR1;
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
|
RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
|
||||||
fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName()));
|
fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName()));
|
||||||
|
fooRealm.setSsoSessionIdleTimeout(1800);
|
||||||
|
fooRealm.setSsoSessionMaxLifespan(36000);
|
||||||
|
fooRealm.setOfflineSessionIdleTimeout(2592000);
|
||||||
|
fooRealm.setOfflineSessionMaxLifespan(5184000);
|
||||||
fooRealm.addClient("foo-app");
|
fooRealm.addClient("foo-app");
|
||||||
currentSession.users().addUser(fooRealm, "user3");
|
currentSession.users().addUser(fooRealm, "user3");
|
||||||
|
|
||||||
|
@ -296,9 +278,12 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR1) -> {
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR1) -> {
|
||||||
currentSession = sessionCR1;
|
currentSession = sessionCR1;
|
||||||
sessionManager = new UserSessionManager(currentSession);
|
sessionManager = new UserSessionManager(currentSession);
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
|
RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
|
||||||
fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName()));
|
fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName()));
|
||||||
|
fooRealm.setSsoSessionIdleTimeout(1800);
|
||||||
|
fooRealm.setSsoSessionMaxLifespan(36000);
|
||||||
|
fooRealm.setOfflineSessionIdleTimeout(2592000);
|
||||||
|
fooRealm.setOfflineSessionMaxLifespan(5184000);
|
||||||
|
|
||||||
fooRealm.addClient("foo-app");
|
fooRealm.addClient("foo-app");
|
||||||
fooRealm.addClient("bar-app");
|
fooRealm.addClient("bar-app");
|
||||||
|
@ -392,6 +377,10 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
currentSession = sessionUR1;
|
currentSession = sessionUR1;
|
||||||
RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
|
RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
|
||||||
fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName()));
|
fooRealm.setDefaultRole(currentSession.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName()));
|
||||||
|
fooRealm.setSsoSessionIdleTimeout(1800);
|
||||||
|
fooRealm.setSsoSessionMaxLifespan(36000);
|
||||||
|
fooRealm.setOfflineSessionIdleTimeout(2592000);
|
||||||
|
fooRealm.setOfflineSessionMaxLifespan(5184000);
|
||||||
fooRealm.addClient("foo-app");
|
fooRealm.addClient("foo-app");
|
||||||
currentSession.users().addUser(fooRealm, "user3");
|
currentSession.users().addUser(fooRealm, "user3");
|
||||||
|
|
||||||
|
@ -443,141 +432,6 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@ModelTest
|
|
||||||
public void testExpired(KeycloakSession session) {
|
|
||||||
// Suspend periodic tasks to avoid race-conditions, which may cause missing updates of lastSessionRefresh times to UserSessionPersisterProvider
|
|
||||||
TimerProvider timer = session.getProvider(TimerProvider.class);
|
|
||||||
TimerProvider.TimerTaskContext timerTaskCtx = timer.cancelTask(PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
|
||||||
log.info("Cancelled periodic task " + PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
|
||||||
|
|
||||||
try {
|
|
||||||
AtomicReference<UserSessionModel[]> origSessionsAt = new AtomicReference<>();
|
|
||||||
|
|
||||||
// Key is userSessionId, value is set of client UUIDS
|
|
||||||
Map<String, Set<String>> offlineSessions = new HashMap<>();
|
|
||||||
ClientModel[] testApp = new ClientModel[1];
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired1) -> {
|
|
||||||
// Create some online sessions in infinispan
|
|
||||||
currentSession = sessionExpired1;
|
|
||||||
reloadState(currentSession);
|
|
||||||
UserSessionModel[] origSessions = createSessions(currentSession);
|
|
||||||
origSessionsAt.set(origSessions);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired2) -> {
|
|
||||||
currentSession = sessionExpired2;
|
|
||||||
realm = currentSession.realms().getRealm("test");
|
|
||||||
sessionManager = new UserSessionManager(currentSession);
|
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
// Persist 3 created userSessions and clientSessions as offline
|
|
||||||
testApp[0] = realm.getClientByClientId("test-app");
|
|
||||||
currentSession.sessions().getUserSessionsStream(realm, testApp[0]).collect(Collectors.toList())
|
|
||||||
.forEach(userSession -> offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(currentSession, userSession)));
|
|
||||||
|
|
||||||
// Assert all previously saved offline sessions found
|
|
||||||
for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
|
|
||||||
UserSessionModel foundSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
|
|
||||||
Assert.assertEquals(foundSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info("Persisted 3 sessions to UserSessionPersisterProvider");
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired3) -> {
|
|
||||||
currentSession = sessionExpired3;
|
|
||||||
realm = currentSession.realms().getRealm("test");
|
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
UserSessionModel[] origSessions = origSessionsAt.get();
|
|
||||||
|
|
||||||
UserSessionModel session0 = currentSession.sessions().getOfflineUserSession(realm, origSessions[0].getId());
|
|
||||||
Assert.assertNotNull(session0);
|
|
||||||
|
|
||||||
// sessions are in persister too
|
|
||||||
Assert.assertEquals(3, persister.getUserSessionsCount(true));
|
|
||||||
|
|
||||||
Time.setOffset(300);
|
|
||||||
log.infof("Set time offset to 300. Time is: %d", Time.currentTime());
|
|
||||||
|
|
||||||
// Set lastSessionRefresh to currentSession[0] to 0
|
|
||||||
session0.setLastSessionRefresh(Time.currentTime());
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Increase timeOffset and update LSR of the session two times - first to 20 days and then to 21 days. At least one of updates
|
|
||||||
// will propagate to PersisterLastSessionRefreshStore and update DB (Single update is not 100% sure as there is still a
|
|
||||||
// chance of delayed periodic task to be run in the meantime and causing race-condition, which would mean LSR not updated in the DB)
|
|
||||||
for (int i=0 ; i<2 ; i++) {
|
|
||||||
int timeOffset = 1728000 + (i * 86400);
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired4) -> {
|
|
||||||
currentSession = sessionExpired4;
|
|
||||||
realm = currentSession.realms().getRealm("test");
|
|
||||||
UserSessionModel[] origSessions = origSessionsAt.get();
|
|
||||||
Time.setOffset(timeOffset);
|
|
||||||
log.infof("Set time offset to %d. Time is: %d", timeOffset, Time.currentTime());
|
|
||||||
|
|
||||||
UserSessionModel session0 = currentSession.sessions().getOfflineUserSession(realm, origSessions[0].getId());
|
|
||||||
session0.setLastSessionRefresh(Time.currentTime());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired5) -> {
|
|
||||||
currentSession = sessionExpired5;
|
|
||||||
realm = currentSession.realms().getRealm("test");
|
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
// Increase timeOffset - 40 days
|
|
||||||
Time.setOffset(3456000);
|
|
||||||
log.infof("Set time offset to 3456000. Time is: %d", Time.currentTime());
|
|
||||||
|
|
||||||
// Expire and ensure that all sessions despite session0 were removed
|
|
||||||
currentSession.sessions().removeExpired(realm);
|
|
||||||
persister.removeExpired(realm);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired6) -> {
|
|
||||||
currentSession = sessionExpired6;
|
|
||||||
realm = currentSession.realms().getRealm("test");
|
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
UserSessionModel[] origSessions = origSessionsAt.get();
|
|
||||||
|
|
||||||
// assert session0 is the only session found
|
|
||||||
Assert.assertNotNull(currentSession.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
|
|
||||||
Assert.assertNull(currentSession.sessions().getOfflineUserSession(realm, origSessions[1].getId()));
|
|
||||||
Assert.assertNull(currentSession.sessions().getOfflineUserSession(realm, origSessions[2].getId()));
|
|
||||||
|
|
||||||
Assert.assertEquals(1, persister.getUserSessionsCount(true));
|
|
||||||
|
|
||||||
// Expire everything and assert nothing found
|
|
||||||
Time.setOffset(7000000);
|
|
||||||
|
|
||||||
currentSession.sessions().removeExpired(realm);
|
|
||||||
persister.removeExpired(realm);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired7) -> {
|
|
||||||
currentSession = sessionExpired7;
|
|
||||||
realm = currentSession.realms().getRealm("test");
|
|
||||||
sessionManager = new UserSessionManager(currentSession);
|
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
|
|
||||||
for (String userSessionId : offlineSessions.keySet()) {
|
|
||||||
Assert.assertNull(sessionManager.findOfflineUserSession(realm, userSessionId));
|
|
||||||
}
|
|
||||||
Assert.assertEquals(0, persister.getUserSessionsCount(true));
|
|
||||||
});
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
Time.setOffset(0);
|
|
||||||
session.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
|
||||||
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static Set<String> createOfflineSessionIncludeClientSessions(KeycloakSession session, UserSessionModel
|
private static Set<String> createOfflineSessionIncludeClientSessions(KeycloakSession session, UserSessionModel
|
||||||
userSession) {
|
userSession) {
|
||||||
Set<String> offlineSessions = new HashSet<>();
|
Set<String> offlineSessions = new HashSet<>();
|
||||||
|
@ -655,7 +509,6 @@ public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTes
|
||||||
currentSession.users().addUser(realm, "user2").setEmail("user2@localhost");
|
currentSession.users().addUser(realm, "user2").setEmail("user2@localhost");
|
||||||
}
|
}
|
||||||
sessionManager = new UserSessionManager(currentSession);
|
sessionManager = new UserSessionManager(currentSession);
|
||||||
persister = currentSession.getProvider(UserSessionPersisterProvider.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -26,7 +26,6 @@ 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.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserLoginFailureModel;
|
import org.keycloak.models.UserLoginFailureModel;
|
||||||
import org.keycloak.models.UserManager;
|
import org.keycloak.models.UserManager;
|
||||||
|
@ -47,12 +46,11 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
|
@ -121,9 +119,10 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
public void testUpdateSession(KeycloakSession session) {
|
public void testUpdateSession(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
UserSessionModel[] sessions = createSessions(session);
|
UserSessionModel[] sessions = createSessions(session);
|
||||||
session.sessions().getUserSession(realm, sessions[0].getId()).setLastSessionRefresh(1000);
|
int lastRefresh = Time.currentTime();
|
||||||
|
session.sessions().getUserSession(realm, sessions[0].getId()).setLastSessionRefresh(lastRefresh);
|
||||||
|
|
||||||
assertEquals(1000, session.sessions().getUserSession(realm, sessions[0].getId()).getLastSessionRefresh());
|
assertEquals(lastRefresh, session.sessions().getUserSession(realm, sessions[0].getId()).getLastSessionRefresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -131,8 +130,9 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
public void testUpdateSessionInSameTransaction(KeycloakSession session) {
|
public void testUpdateSessionInSameTransaction(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
UserSessionModel[] sessions = createSessions(session);
|
UserSessionModel[] sessions = createSessions(session);
|
||||||
session.sessions().getUserSession(realm, sessions[0].getId()).setLastSessionRefresh(1000);
|
int lastRefresh = Time.currentTime();
|
||||||
assertEquals(1000, session.sessions().getUserSession(realm, sessions[0].getId()).getLastSessionRefresh());
|
session.sessions().getUserSession(realm, sessions[0].getId()).setLastSessionRefresh(lastRefresh);
|
||||||
|
assertEquals(lastRefresh, session.sessions().getUserSession(realm, sessions[0].getId()).getLastSessionRefresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -249,13 +249,6 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
UserSessionModel[] sessions = createSessions(session);
|
UserSessionModel[] sessions = createSessions(session);
|
||||||
|
|
||||||
KeycloakTransaction transaction = session.getTransactionManager();
|
|
||||||
if (!transaction.getRollbackOnly()) {
|
|
||||||
transaction.commit();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
assertSessions(session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername(realm, "user1"))
|
assertSessions(session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername(realm, "user1"))
|
||||||
.collect(Collectors.toList()), sessions[0], sessions[1]);
|
.collect(Collectors.toList()), sessions[0], sessions[1]);
|
||||||
assertSessions(session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername(realm, "user2"))
|
assertSessions(session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername(realm, "user2"))
|
||||||
|
@ -266,11 +259,8 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
@ModelTest
|
@ModelTest
|
||||||
public void testRemoveUserSessionsByUser(KeycloakSession session) {
|
public void testRemoveUserSessionsByUser(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
createSessions(session);
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession kcSession) -> {
|
|
||||||
inheritClientConnection(session, kcSession);
|
|
||||||
createSessions(kcSession);
|
|
||||||
});
|
|
||||||
Map<String, Integer> clientSessionsKept = session.sessions().getUserSessionsStream(realm,
|
Map<String, Integer> clientSessionsKept = session.sessions().getUserSessionsStream(realm,
|
||||||
session.users().getUserByUsername(realm, "user2"))
|
session.users().getUserByUsername(realm, "user2"))
|
||||||
.collect(Collectors.toMap(model -> model.getId(), model -> model.getAuthenticatedClientSessions().keySet().size()));
|
.collect(Collectors.toMap(model -> model.getId(), model -> model.getAuthenticatedClientSessions().keySet().size()));
|
||||||
|
@ -307,10 +297,7 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
@ModelTest
|
@ModelTest
|
||||||
public void testRemoveUserSessionsByRealm(KeycloakSession session) {
|
public void testRemoveUserSessionsByRealm(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession kcSession) -> {
|
createSessions(session);
|
||||||
inheritClientConnection(session, kcSession);
|
|
||||||
createSessions(kcSession);
|
|
||||||
});
|
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession kcSession) -> {
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession kcSession) -> {
|
||||||
kcSession.sessions().removeUserSessions(realm);
|
kcSession.sessions().removeUserSessions(realm);
|
||||||
|
@ -411,12 +398,13 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
public void testTransientUserSession(KeycloakSession session) {
|
public void testTransientUserSession(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
ClientModel client = realm.getClientByClientId("test-app");
|
ClientModel client = realm.getClientByClientId("test-app");
|
||||||
|
String userSessionId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
// create an user session, but don't persist it to infinispan
|
// create an user session, but don't persist it to infinispan
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession session1) -> {
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession session1) -> {
|
||||||
long sessionsBefore = session1.sessions().getActiveUserSessions(realm, client);
|
long sessionsBefore = session1.sessions().getActiveUserSessions(realm, client);
|
||||||
|
|
||||||
UserSessionModel userSession = session1.sessions().createUserSession("123", realm, session1.users().getUserByUsername(realm, "user1"),
|
UserSessionModel userSession = session1.sessions().createUserSession(userSessionId, realm, session1.users().getUserByUsername(realm, "user1"),
|
||||||
"user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
|
"user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
|
||||||
AuthenticatedClientSessionModel clientSession = session1.sessions().createClientSession(realm, client, userSession);
|
AuthenticatedClientSessionModel clientSession = session1.sessions().createClientSession(realm, client, userSession);
|
||||||
assertEquals(userSession, clientSession.getUserSession());
|
assertEquals(userSession, clientSession.getUserSession());
|
||||||
|
@ -424,7 +412,7 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
assertSession(userSession, session.users().getUserByUsername(realm, "user1"), "127.0.0.1", userSession.getStarted(), userSession.getStarted(), "test-app");
|
assertSession(userSession, session.users().getUserByUsername(realm, "user1"), "127.0.0.1", userSession.getStarted(), userSession.getStarted(), "test-app");
|
||||||
|
|
||||||
// Can find session by ID in current transaction
|
// Can find session by ID in current transaction
|
||||||
UserSessionModel foundSession = session1.sessions().getUserSession(realm, "123");
|
UserSessionModel foundSession = session1.sessions().getUserSession(realm, userSessionId);
|
||||||
Assert.assertEquals(userSession, foundSession);
|
Assert.assertEquals(userSession, foundSession);
|
||||||
|
|
||||||
// Count of sessions should be still the same
|
// Count of sessions should be still the same
|
||||||
|
@ -433,7 +421,7 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
// create an user session whose last refresh exceeds the max session idle timeout.
|
// create an user session whose last refresh exceeds the max session idle timeout.
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession session1) -> {
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession session1) -> {
|
||||||
UserSessionModel userSession = session1.sessions().getUserSession(realm, "123");
|
UserSessionModel userSession = session1.sessions().getUserSession(realm, userSessionId);
|
||||||
Assert.assertNull(userSession);
|
Assert.assertNull(userSession);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -548,12 +536,6 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
UserSessionModel[] sessions = createSessions(session);
|
UserSessionModel[] sessions = createSessions(session);
|
||||||
|
|
||||||
KeycloakTransaction transaction = session.getTransactionManager();
|
|
||||||
if (!transaction.getRollbackOnly()) {
|
|
||||||
transaction.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
assertSessions(session.sessions().getUserSessionsStream(realm, realm.getClientByClientId("test-app"))
|
assertSessions(session.sessions().getUserSessionsStream(realm, realm.getClientByClientId("test-app"))
|
||||||
.collect(Collectors.toList()), sessions[0], sessions[1], sessions[2]);
|
.collect(Collectors.toList()), sessions[0], sessions[1], sessions[2]);
|
||||||
assertSessions(session.sessions().getUserSessionsStream(realm, realm.getClientByClientId("third-party"))
|
assertSessions(session.sessions().getUserSessionsStream(realm, realm.getClientByClientId("third-party"))
|
||||||
|
@ -564,25 +546,23 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
@ModelTest
|
@ModelTest
|
||||||
public void testGetByClientPaginated(KeycloakSession session) {
|
public void testGetByClientPaginated(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
try {
|
|
||||||
for (int i = 0; i < 25; i++) {
|
|
||||||
Time.setOffset(i);
|
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0." + i, "form", false, null, null);
|
|
||||||
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"), userSession);
|
|
||||||
assertNotNull(clientSession);
|
|
||||||
clientSession.setRedirectUri("http://redirect");
|
|
||||||
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state");
|
|
||||||
clientSession.setTimestamp(userSession.getStarted());
|
|
||||||
userSession.setLastSessionRefresh(userSession.getStarted());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
Time.setOffset(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakTransaction transaction = session.getTransactionManager();
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession kcSession) -> {
|
||||||
if (!transaction.getRollbackOnly()) {
|
try {
|
||||||
transaction.commit();
|
for (int i = 0; i < 25; i++) {
|
||||||
}
|
Time.setOffset(i);
|
||||||
|
UserSessionModel userSession = kcSession.sessions().createUserSession(realm, kcSession.users().getUserByUsername(realm, "user1"), "user1", "127.0.0." + i, "form", false, null, null);
|
||||||
|
AuthenticatedClientSessionModel clientSession = kcSession.sessions().createClientSession(realm, realm.getClientByClientId("test-app"), userSession);
|
||||||
|
assertNotNull(clientSession);
|
||||||
|
clientSession.setRedirectUri("http://redirect");
|
||||||
|
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state");
|
||||||
|
clientSession.setTimestamp(userSession.getStarted());
|
||||||
|
userSession.setLastSessionRefresh(userSession.getStarted());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Time.setOffset(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
assertPaginatedSession(session, realm, realm.getClientByClientId("test-app"), 0, 1, 1);
|
assertPaginatedSession(session, realm, realm.getClientByClientId("test-app"), 0, 1, 1);
|
||||||
assertPaginatedSession(session, realm, realm.getClientByClientId("test-app"), 0, 10, 10);
|
assertPaginatedSession(session, realm, realm.getClientByClientId("test-app"), 0, 10, 10);
|
||||||
|
@ -612,6 +592,8 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
@ModelTest
|
@ModelTest
|
||||||
public void testAuthenticatedClientSessions(KeycloakSession session) {
|
public void testAuthenticatedClientSessions(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
realm.setSsoSessionIdleTimeout(1800);
|
||||||
|
realm.setSsoSessionMaxLifespan(36000);
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null);
|
||||||
|
|
||||||
ClientModel client1 = realm.getClientByClientId("test-app");
|
ClientModel client1 = realm.getClientByClientId("test-app");
|
||||||
|
@ -620,19 +602,21 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
// Create client1 session
|
// Create client1 session
|
||||||
AuthenticatedClientSessionModel clientSession1 = session.sessions().createClientSession(realm, client1, userSession);
|
AuthenticatedClientSessionModel clientSession1 = session.sessions().createClientSession(realm, client1, userSession);
|
||||||
clientSession1.setAction("foo1");
|
clientSession1.setAction("foo1");
|
||||||
clientSession1.setTimestamp(100);
|
int currentTime1 = Time.currentTime();
|
||||||
|
clientSession1.setTimestamp(currentTime1);
|
||||||
|
|
||||||
// Create client2 session
|
// Create client2 session
|
||||||
AuthenticatedClientSessionModel clientSession2 = session.sessions().createClientSession(realm, client2, userSession);
|
AuthenticatedClientSessionModel clientSession2 = session.sessions().createClientSession(realm, client2, userSession);
|
||||||
clientSession2.setAction("foo2");
|
clientSession2.setAction("foo2");
|
||||||
clientSession2.setTimestamp(200);
|
int currentTime2 = Time.currentTime();
|
||||||
|
clientSession2.setTimestamp(currentTime2);
|
||||||
|
|
||||||
// Ensure sessions are here
|
// Ensure sessions are here
|
||||||
userSession = session.sessions().getUserSession(realm, userSession.getId());
|
userSession = session.sessions().getUserSession(realm, userSession.getId());
|
||||||
Map<String, AuthenticatedClientSessionModel> clientSessions = userSession.getAuthenticatedClientSessions();
|
Map<String, AuthenticatedClientSessionModel> clientSessions = userSession.getAuthenticatedClientSessions();
|
||||||
Assert.assertEquals(2, clientSessions.size());
|
Assert.assertEquals(2, clientSessions.size());
|
||||||
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1", 100);
|
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1", currentTime1);
|
||||||
testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2", 200);
|
testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2", currentTime2);
|
||||||
|
|
||||||
// Update session1
|
// Update session1
|
||||||
clientSessions.get(client1.getId()).setAction("foo1-updated");
|
clientSessions.get(client1.getId()).setAction("foo1-updated");
|
||||||
|
@ -641,20 +625,21 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
// Ensure updated
|
// Ensure updated
|
||||||
userSession = session.sessions().getUserSession(realm, userSession.getId());
|
userSession = session.sessions().getUserSession(realm, userSession.getId());
|
||||||
clientSessions = userSession.getAuthenticatedClientSessions();
|
clientSessions = userSession.getAuthenticatedClientSessions();
|
||||||
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
|
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", currentTime1);
|
||||||
|
|
||||||
// Rewrite session2
|
// Rewrite session2
|
||||||
clientSession2 = session.sessions().createClientSession(realm, client2, userSession);
|
clientSession2 = session.sessions().createClientSession(realm, client2, userSession);
|
||||||
clientSession2.setAction("foo2-rewrited");
|
clientSession2.setAction("foo2-rewrited");
|
||||||
clientSession2.setTimestamp(300);
|
int currentTime3 = Time.currentTime();
|
||||||
|
clientSession2.setTimestamp(currentTime3);
|
||||||
|
|
||||||
|
|
||||||
// Ensure updated
|
// Ensure updated
|
||||||
userSession = session.sessions().getUserSession(realm, userSession.getId());
|
userSession = session.sessions().getUserSession(realm, userSession.getId());
|
||||||
clientSessions = userSession.getAuthenticatedClientSessions();
|
clientSessions = userSession.getAuthenticatedClientSessions();
|
||||||
Assert.assertEquals(2, clientSessions.size());
|
Assert.assertEquals(2, clientSessions.size());
|
||||||
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
|
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", currentTime1);
|
||||||
testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2-rewrited", 300);
|
testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2-rewrited", currentTime3);
|
||||||
|
|
||||||
// remove session
|
// remove session
|
||||||
clientSession1 = userSession.getAuthenticatedClientSessions().get(client1.getId());
|
clientSession1 = userSession.getAuthenticatedClientSessions().get(client1.getId());
|
||||||
|
@ -675,19 +660,7 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertPaginatedSession(KeycloakSession session, RealmModel realm, ClientModel client, int start, int max, int expectedSize) {
|
private static void assertPaginatedSession(KeycloakSession session, RealmModel realm, ClientModel client, int start, int max, int expectedSize) {
|
||||||
List<UserSessionModel> sessions = session.sessions().getUserSessionsStream(realm, client, start, max).collect(Collectors.toList());
|
assertEquals(expectedSize, session.sessions().getUserSessionsStream(realm, client, start, max).count());
|
||||||
String[] actualIps = new String[sessions.size()];
|
|
||||||
|
|
||||||
for (int i = 0; i < actualIps.length; i++) {
|
|
||||||
actualIps[i] = sessions.get(i).getIpAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] expectedIps = new String[expectedSize];
|
|
||||||
for (int i = 0; i < expectedSize; i++) {
|
|
||||||
expectedIps[i] = "127.0.0." + (i + start);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertArrayEquals(expectedIps, actualIps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -698,60 +671,61 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
createSessions(session);
|
createSessions(session);
|
||||||
|
|
||||||
KeycloakTransaction transaction = session.getTransactionManager();
|
|
||||||
if (!transaction.getRollbackOnly()) {
|
|
||||||
transaction.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(3, session.sessions().getActiveUserSessions(realm, realm.getClientByClientId("test-app")));
|
assertEquals(3, session.sessions().getActiveUserSessions(realm, realm.getClientByClientId("test-app")));
|
||||||
assertEquals(1, session.sessions().getActiveUserSessions(realm, realm.getClientByClientId("third-party")));
|
assertEquals(1, session.sessions().getActiveUserSessions(realm, realm.getClientByClientId("third-party")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loginFailures() {
|
public void loginFailures() {
|
||||||
testingClient.server().run(UserSessionProviderTest::loginFailures);
|
testingClient.server().run((KeycloakSession kcSession) -> {
|
||||||
}
|
RealmModel realm = kcSession.realms().getRealmByName("test");
|
||||||
public static void loginFailures(KeycloakSession session) {
|
UserLoginFailureModel failure1 = kcSession.loginFailures().addUserLoginFailure(realm, "user1");
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
failure1.incrementFailures();
|
||||||
UserLoginFailureModel failure1 = session.sessions().addUserLoginFailure(realm, "user1");
|
|
||||||
failure1.incrementFailures();
|
|
||||||
|
|
||||||
UserLoginFailureModel failure2 = session.sessions().addUserLoginFailure(realm, "user2");
|
UserLoginFailureModel failure2 = kcSession.loginFailures().addUserLoginFailure(realm, "user2");
|
||||||
failure2.incrementFailures();
|
failure2.incrementFailures();
|
||||||
failure2.incrementFailures();
|
failure2.incrementFailures();
|
||||||
|
});
|
||||||
|
|
||||||
session.getTransactionManager().commit();
|
testingClient.server().run((KeycloakSession kcSession) -> {
|
||||||
|
RealmModel realm = kcSession.realms().getRealmByName("test");
|
||||||
|
|
||||||
failure1 = session.sessions().getUserLoginFailure(realm, "user1");
|
UserLoginFailureModel failure1 = kcSession.loginFailures().getUserLoginFailure(realm, "user1");
|
||||||
assertEquals(1, failure1.getNumFailures());
|
assertEquals(1, failure1.getNumFailures());
|
||||||
|
|
||||||
failure2 = session.sessions().getUserLoginFailure(realm, "user2");
|
UserLoginFailureModel failure2 = kcSession.loginFailures().getUserLoginFailure(realm, "user2");
|
||||||
assertEquals(2, failure2.getNumFailures());
|
assertEquals(2, failure2.getNumFailures());
|
||||||
|
|
||||||
//session.getTransactionManager().commit();
|
// Add the failure, which already exists
|
||||||
|
failure1.incrementFailures();
|
||||||
|
|
||||||
// Add the failure, which already exists
|
assertEquals(2, failure1.getNumFailures());
|
||||||
//failure1 = session.sessions().addUserLoginFailure(realm, "user1");
|
|
||||||
failure1.incrementFailures();
|
|
||||||
|
|
||||||
//failure1 = session.sessions().getUserLoginFailure(realm, "user1");
|
failure1 = kcSession.loginFailures().getUserLoginFailure(realm, "user1");
|
||||||
assertEquals(2, failure1.getNumFailures());
|
failure1.clearFailures();
|
||||||
|
|
||||||
failure1 = session.sessions().getUserLoginFailure(realm, "user1");
|
failure1 = kcSession.loginFailures().getUserLoginFailure(realm, "user1");
|
||||||
failure1.clearFailures();
|
assertEquals(0, failure1.getNumFailures());
|
||||||
|
});
|
||||||
|
|
||||||
session.getTransactionManager().commit();
|
testingClient.server().run((KeycloakSession kcSession) -> {
|
||||||
|
RealmModel realm = kcSession.realms().getRealmByName("test");
|
||||||
|
kcSession.loginFailures().removeUserLoginFailure(realm, "user1");
|
||||||
|
});
|
||||||
|
|
||||||
failure1 = session.sessions().getUserLoginFailure(realm, "user1");
|
testingClient.server().run((KeycloakSession kcSession) -> {
|
||||||
assertEquals(0, failure1.getNumFailures());
|
RealmModel realm = kcSession.realms().getRealmByName("test");
|
||||||
|
|
||||||
session.sessions().removeUserLoginFailure(realm, "user1");
|
assertNull(kcSession.loginFailures().getUserLoginFailure(realm, "user1"));
|
||||||
session.sessions().removeUserLoginFailure(realm, "user2");
|
|
||||||
|
|
||||||
assertNull(session.sessions().getUserLoginFailure(realm, "user1"));
|
kcSession.loginFailures().removeAllUserLoginFailures(realm);
|
||||||
|
});
|
||||||
|
|
||||||
session.sessions().removeAllUserLoginFailures(realm);
|
testingClient.server().run((KeycloakSession kcSession) -> {
|
||||||
assertNull(session.sessions().getUserLoginFailure(realm, "user2"));
|
RealmModel realm = kcSession.realms().getRealmByName("test");
|
||||||
|
assertNull(kcSession.loginFailures().getUserLoginFailure(realm, "user1"));
|
||||||
|
assertNull(kcSession.loginFailures().getUserLoginFailure(realm, "user2"));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -760,43 +734,20 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
public static void testOnUserRemoved(KeycloakSession session) {
|
public static void testOnUserRemoved(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
|
||||||
UserModel user1 = session.users().getUserByUsername(realm, "user1");
|
UserModel user1 = session.users().getUserByUsername(realm, "user1");
|
||||||
UserModel user2 = session.users().getUserByUsername(realm, "user2");
|
UserModel user2 = session.users().getUserByUsername(realm, "user2");
|
||||||
|
|
||||||
UserSessionModel[] sessions = new UserSessionModel[3];
|
createSessions(session);
|
||||||
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null);
|
|
||||||
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state");
|
assertEquals(2, session.sessions().getUserSessionsStream(realm, user1).count());
|
||||||
createClientSession(session, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state");
|
assertEquals(1, session.sessions().getUserSessionsStream(realm, user2).count());
|
||||||
|
|
||||||
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null);
|
// remove user1
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state");
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession kcSession) ->
|
||||||
|
(new UserManager(kcSession)).removeUser(realm, user1));
|
||||||
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user2"), "user2", "127.0.0.3", "form", true, null, null);
|
|
||||||
//createClientSession(session, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state");
|
|
||||||
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"), sessions[2]);
|
|
||||||
clientSession.setRedirectUri("http://redirct");
|
|
||||||
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state");
|
|
||||||
|
|
||||||
|
|
||||||
session.sessions().addUserLoginFailure(realm, user1.getId());
|
|
||||||
session.sessions().addUserLoginFailure(realm, user2.getId());
|
|
||||||
|
|
||||||
session.userStorageManager().removeUser(realm, user1);
|
|
||||||
|
|
||||||
assertEquals(0, session.sessions().getUserSessionsStream(realm, user1).count());
|
assertEquals(0, session.sessions().getUserSessionsStream(realm, user1).count());
|
||||||
|
assertEquals(1, session.sessions().getUserSessionsStream(realm, user2).count());
|
||||||
session.getTransactionManager().commit();
|
|
||||||
|
|
||||||
assertNotEquals(0, session.sessions().getUserSessionsStream(realm, session.users().getUserByUsername(realm, "user2")).count());
|
|
||||||
|
|
||||||
user1 = session.users().getUserByUsername(realm, "user1");
|
|
||||||
user2 = session.users().getUserByUsername(realm, "user2");
|
|
||||||
|
|
||||||
// it seems as if Null does not happen with the new test suite. The sizes of these are ZERO so the removes worked at this point.
|
|
||||||
//assertNull(session.sessions().getUserLoginFailure(realm, user1.getId()));
|
|
||||||
//assertNotNull(session.sessions().getUserLoginFailure(realm, user2.getId()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AuthenticatedClientSessionModel createClientSession(KeycloakSession session, ClientModel client, UserSessionModel userSession, String redirect, String state) {
|
private static AuthenticatedClientSessionModel createClientSession(KeycloakSession session, ClientModel client, UserSessionModel userSession, String redirect, String state) {
|
||||||
|
@ -808,20 +759,21 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UserSessionModel[] createSessions(KeycloakSession session) {
|
private static UserSessionModel[] createSessions(KeycloakSession session) {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
|
||||||
UserSessionModel[] sessions = new UserSessionModel[3];
|
UserSessionModel[] sessions = new UserSessionModel[3];
|
||||||
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null);
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession kcSession) -> {
|
||||||
|
RealmModel realm = kcSession.realms().getRealmByName("test");
|
||||||
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state");
|
sessions[0] = kcSession.sessions().createUserSession(realm, kcSession.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null);
|
||||||
createClientSession(session, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state");
|
|
||||||
|
|
||||||
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null);
|
createClientSession(kcSession, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state");
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state");
|
createClientSession(kcSession, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state");
|
||||||
|
|
||||||
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user2"), "user2", "127.0.0.3", "form", true, null, null);
|
|
||||||
createClientSession(session, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state");
|
|
||||||
|
|
||||||
|
sessions[1] = kcSession.sessions().createUserSession(realm, kcSession.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null);
|
||||||
|
createClientSession(kcSession, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state");
|
||||||
|
|
||||||
|
sessions[2] = kcSession.sessions().createUserSession(realm, kcSession.users().getUserByUsername(realm, "user2"), "user2", "127.0.0.3", "form", true, null, null);
|
||||||
|
createClientSession(kcSession, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state");
|
||||||
|
});
|
||||||
|
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -529,8 +529,7 @@ public class BackchannelLogoutTest extends AbstractNestedBrokerTest {
|
||||||
|
|
||||||
assertNoSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
assertNoSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
||||||
sessionIdConsumerRealm);
|
sessionIdConsumerRealm);
|
||||||
assertNoOfflineSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
assertNoOfflineSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm);
|
||||||
sessionIdConsumerRealm);
|
|
||||||
assertNoSessionsInClient(nbc.subConsumerRealmName(), accountClientIdSubConsumerRealm, userIdSubConsumerRealm,
|
assertNoSessionsInClient(nbc.subConsumerRealmName(), accountClientIdSubConsumerRealm, userIdSubConsumerRealm,
|
||||||
sessionIdSubConsumerRealm);
|
sessionIdSubConsumerRealm);
|
||||||
assertActiveSessionInClient(nbc.providerRealmName(), brokerClientIdProviderRealm, userIdProviderRealm,
|
assertActiveSessionInClient(nbc.providerRealmName(), brokerClientIdProviderRealm, userIdProviderRealm,
|
||||||
|
@ -570,8 +569,7 @@ public class BackchannelLogoutTest extends AbstractNestedBrokerTest {
|
||||||
|
|
||||||
assertNoSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
assertNoSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
||||||
sessionIdConsumerRealm);
|
sessionIdConsumerRealm);
|
||||||
assertActiveOfflineSessionInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
assertActiveOfflineSessionInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm);
|
||||||
sessionIdConsumerRealm);
|
|
||||||
assertNoSessionsInClient(nbc.subConsumerRealmName(), accountClientIdSubConsumerRealm, userIdSubConsumerRealm,
|
assertNoSessionsInClient(nbc.subConsumerRealmName(), accountClientIdSubConsumerRealm, userIdSubConsumerRealm,
|
||||||
sessionIdSubConsumerRealm);
|
sessionIdSubConsumerRealm);
|
||||||
assertActiveSessionInClient(nbc.providerRealmName(), brokerClientIdProviderRealm, userIdProviderRealm,
|
assertActiveSessionInClient(nbc.providerRealmName(), brokerClientIdProviderRealm, userIdProviderRealm,
|
||||||
|
@ -596,8 +594,7 @@ public class BackchannelLogoutTest extends AbstractNestedBrokerTest {
|
||||||
logoutFromRealm(getConsumerRoot(), nbc.consumerRealmName());
|
logoutFromRealm(getConsumerRoot(), nbc.consumerRealmName());
|
||||||
assertNoSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
assertNoSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
||||||
sessionIdConsumerRealm);
|
sessionIdConsumerRealm);
|
||||||
assertActiveOfflineSessionInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
assertActiveOfflineSessionInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm);
|
||||||
sessionIdConsumerRealm);
|
|
||||||
|
|
||||||
String logoutTokenEncoded = getLogoutTokenEncodedAndSigned(userIdProviderRealm, sessionIdProviderRealm, true);
|
String logoutTokenEncoded = getLogoutTokenEncodedAndSigned(userIdProviderRealm, sessionIdProviderRealm, true);
|
||||||
|
|
||||||
|
@ -606,8 +603,7 @@ public class BackchannelLogoutTest extends AbstractNestedBrokerTest {
|
||||||
assertThat(response, Matchers.statusCodeIsHC(Response.Status.OK));
|
assertThat(response, Matchers.statusCodeIsHC(Response.Status.OK));
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNoOfflineSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm,
|
assertNoOfflineSessionsInClient(nbc.consumerRealmName(), consumerClientId, userIdConsumerRealm);
|
||||||
sessionIdConsumerRealm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void subConsumerIdpRequestsOfflineSessions() {
|
private void subConsumerIdpRequestsOfflineSessions() {
|
||||||
|
@ -790,25 +786,23 @@ public class BackchannelLogoutTest extends AbstractNestedBrokerTest {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertActiveOfflineSessionInClient(String realmName, String clientId, String userId,
|
private void assertActiveOfflineSessionInClient(String realmName, String clientId, String userId) {
|
||||||
String sessionId) {
|
List<UserSessionRepresentation> sessions = getOfflineClientSessions(realmName, clientId, userId);
|
||||||
List<UserSessionRepresentation> sessions = getOfflineClientSessions(realmName, clientId, userId, sessionId);
|
|
||||||
assertThat(sessions.size(), is(1));
|
assertThat(sessions.size(), is(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNoOfflineSessionsInClient(String realmName, String clientId, String userId, String sessionId) {
|
private void assertNoOfflineSessionsInClient(String realmName, String clientId, String userId) {
|
||||||
List<UserSessionRepresentation> sessions = getOfflineClientSessions(realmName, clientId, userId, sessionId);
|
List<UserSessionRepresentation> sessions = getOfflineClientSessions(realmName, clientId, userId);
|
||||||
assertThat(sessions.size(), is(0));
|
assertThat(sessions.size(), is(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UserSessionRepresentation> getOfflineClientSessions(String realmName, String clientUuid, String userId,
|
private List<UserSessionRepresentation> getOfflineClientSessions(String realmName, String clientUuid, String userId) {
|
||||||
String sessionId) {
|
|
||||||
return adminClient.realm(realmName)
|
return adminClient.realm(realmName)
|
||||||
.clients()
|
.clients()
|
||||||
.get(clientUuid)
|
.get(clientUuid)
|
||||||
.getOfflineUserSessions(0, 5)
|
.getOfflineUserSessions(0, 5)
|
||||||
.stream()
|
.stream()
|
||||||
.filter(s -> s.getUserId().equals(userId) && s.getId().equals(sessionId))
|
.filter(s -> s.getUserId().equals(userId))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.jose.jws.JWSHeader;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.models.AdminRoles;
|
import org.keycloak.models.AdminRoles;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
|
@ -255,17 +256,17 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
setTimeOffset(3000000);
|
setTimeOffset(3000000);
|
||||||
|
|
||||||
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(newRefreshTokenString, "secret1");
|
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(newRefreshTokenString, "secret1");
|
||||||
|
RefreshToken newRefreshToken = oauth.parseRefreshToken(newRefreshTokenString);
|
||||||
Assert.assertEquals(400, response.getStatusCode());
|
Assert.assertEquals(400, response.getStatusCode());
|
||||||
assertEquals("invalid_grant", response.getError());
|
assertEquals("invalid_grant", response.getError());
|
||||||
|
|
||||||
events.expectRefresh(offlineToken.getId(), sessionId)
|
events.expectRefresh(offlineToken.getId(), newRefreshToken.getSessionState())
|
||||||
.client("offline-client")
|
.client("offline-client")
|
||||||
.error(Errors.INVALID_TOKEN)
|
.error(Errors.INVALID_TOKEN)
|
||||||
.user(userId)
|
.user(userId)
|
||||||
.clearDetails()
|
.clearDetails()
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
|
||||||
|
|
||||||
setTimeOffset(0);
|
setTimeOffset(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +288,6 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(offlineTokenString, "secret1");
|
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(offlineTokenString, "secret1");
|
||||||
AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
|
AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
|
||||||
Assert.assertEquals(200, response.getStatusCode());
|
Assert.assertEquals(200, response.getStatusCode());
|
||||||
Assert.assertEquals(sessionId, refreshedToken.getSessionState());
|
|
||||||
|
|
||||||
// Assert new refreshToken in the response
|
// Assert new refreshToken in the response
|
||||||
String newRefreshToken = response.getRefreshToken();
|
String newRefreshToken = response.getRefreshToken();
|
||||||
|
@ -393,7 +393,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
|
||||||
// Refresh with new refreshToken is successful now
|
// Refresh with new refreshToken is successful now
|
||||||
testRefreshWithOfflineToken(token, offlineToken2, offlineTokenString2, token.getSessionState(), userId);
|
testRefreshWithOfflineToken(token, offlineToken2, offlineTokenString2, offlineToken2.getSessionState(), userId);
|
||||||
|
|
||||||
RealmManager.realm(adminClient.realm("test")).revokeRefreshToken(false);
|
RealmManager.realm(adminClient.realm("test")).revokeRefreshToken(false);
|
||||||
}
|
}
|
||||||
|
@ -591,6 +591,40 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
assertEquals(400, response.getStatusCode());
|
assertEquals(400, response.getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onlineOfflineTokenLogout() throws Exception {
|
||||||
|
oauth.clientId("offline-client");
|
||||||
|
|
||||||
|
// create online session
|
||||||
|
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password");
|
||||||
|
assertEquals(200, response.getStatusCode());
|
||||||
|
|
||||||
|
// assert refresh token
|
||||||
|
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret1");
|
||||||
|
assertEquals(200, response.getStatusCode());
|
||||||
|
|
||||||
|
// create offline session
|
||||||
|
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||||
|
OAuthClient.AccessTokenResponse offlineResponse = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password");
|
||||||
|
assertEquals(200, offlineResponse.getStatusCode());
|
||||||
|
|
||||||
|
// assert refresh offline token
|
||||||
|
OAuthClient.AccessTokenResponse offlineRefresh = oauth.doRefreshTokenRequest(offlineResponse.getRefreshToken(), "secret1");
|
||||||
|
assertEquals(200, offlineRefresh.getStatusCode());
|
||||||
|
|
||||||
|
// logout online session
|
||||||
|
CloseableHttpResponse logoutResponse = oauth.scope("").doLogout(response.getRefreshToken(), "secret1");
|
||||||
|
assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
|
||||||
|
|
||||||
|
// assert the online session is gone
|
||||||
|
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret1");
|
||||||
|
assertEquals(400, response.getStatusCode());
|
||||||
|
|
||||||
|
// assert the offline token refresh still works
|
||||||
|
offlineRefresh = oauth.doRefreshTokenRequest(offlineResponse.getRefreshToken(), "secret1");
|
||||||
|
assertEquals(200, offlineRefresh.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void browserOfflineTokenLogoutFollowedByLoginSameSession() throws Exception {
|
public void browserOfflineTokenLogoutFollowedByLoginSameSession() throws Exception {
|
||||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||||
|
@ -621,11 +655,14 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||||
assertEquals(0, offlineToken.getExpiration());
|
assertEquals(0, offlineToken.getExpiration());
|
||||||
|
|
||||||
|
String offlineUserSessionId = testingClient.server().fetch((KeycloakSession session) ->
|
||||||
|
session.sessions().getOfflineUserSession(session.realms().getRealmByName("test"), offlineToken.getSessionState()).getId(), String.class);
|
||||||
|
|
||||||
|
// logout offline session
|
||||||
try (CloseableHttpResponse logoutResponse = oauth.doLogout(offlineTokenString, "secret1")) {
|
try (CloseableHttpResponse logoutResponse = oauth.doLogout(offlineTokenString, "secret1")) {
|
||||||
assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
|
assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
|
||||||
}
|
}
|
||||||
|
events.expectLogout(offlineUserSessionId)
|
||||||
events.expectLogout(offlineToken.getSessionState())
|
|
||||||
.client("offline-client")
|
.client("offline-client")
|
||||||
.removeDetail(Details.REDIRECT_URI)
|
.removeDetail(Details.REDIRECT_URI)
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
@ -752,7 +789,6 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
offlineToken = oauth.parseRefreshToken(offlineTokenString);
|
offlineToken = oauth.parseRefreshToken(offlineTokenString);
|
||||||
|
|
||||||
Assert.assertEquals(200, tokenResponse.getStatusCode());
|
Assert.assertEquals(200, tokenResponse.getStatusCode());
|
||||||
Assert.assertEquals(sessionId, refreshedToken.getSessionState());
|
|
||||||
|
|
||||||
// wait to expire
|
// wait to expire
|
||||||
setTimeOffset(offset);
|
setTimeOffset(offset);
|
||||||
|
|
|
@ -64,6 +64,14 @@
|
||||||
"provider": "${keycloak.authSession.provider:infinispan}"
|
"provider": "${keycloak.authSession.provider:infinispan}"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"userSessions": {
|
||||||
|
"provider": "${keycloak.userSession.provider:infinispan}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"loginFailure": {
|
||||||
|
"provider": "${keycloak.loginFailure.provider:infinispan}"
|
||||||
|
},
|
||||||
|
|
||||||
"mapStorage": {
|
"mapStorage": {
|
||||||
"provider": "${keycloak.mapStorage.provider:concurrenthashmap}",
|
"provider": "${keycloak.mapStorage.provider:concurrenthashmap}",
|
||||||
"concurrenthashmap": {
|
"concurrenthashmap": {
|
||||||
|
@ -93,10 +101,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"userSessions": {
|
|
||||||
"provider" : "${keycloak.userSessions.provider:infinispan}"
|
|
||||||
},
|
|
||||||
|
|
||||||
"timer": {
|
"timer": {
|
||||||
"provider": "basic"
|
"provider": "basic"
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
"realm": "test",
|
"realm": "test",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"accessTokenLifespan": 10,
|
"accessTokenLifespan": 10,
|
||||||
"ssoSessionIdleTimeout": 30,
|
"ssoSessionIdleTimeout": 1800,
|
||||||
|
"ssoSessionMaxLifespan": 36000,
|
||||||
|
"offlineSessionIdleTimeout": 2592000,
|
||||||
|
"offlineSessionMaxLifespan": 5184000,
|
||||||
"sslRequired": "external",
|
"sslRequired": "external",
|
||||||
"registrationAllowed": true,
|
"registrationAllowed": true,
|
||||||
"resetPasswordAllowed": true,
|
"resetPasswordAllowed": true,
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
"registrationAllowed": true,
|
"registrationAllowed": true,
|
||||||
"resetPasswordAllowed": true,
|
"resetPasswordAllowed": true,
|
||||||
"editUsernameAllowed" : true,
|
"editUsernameAllowed" : true,
|
||||||
|
"ssoSessionIdleTimeout": 1800,
|
||||||
|
"ssoSessionMaxLifespan": 36000,
|
||||||
|
"offlineSessionIdleTimeout": 2592000,
|
||||||
|
"offlineSessionMaxLifespan": 5184000,
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
"requiredCredentials": [ "password" ],
|
"requiredCredentials": [ "password" ],
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.keycloak.common.util.ResteasyProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
public class ResteasyNullProvider implements ResteasyProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> R getContextData(Class<R> type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pushDefaultContextObject(Class type, Object instance) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pushContext(Class type, Object instance) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearContextData() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#
|
||||||
|
# Copyright 2021 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.
|
||||||
|
|
||||||
|
org.keycloak.testsuite.model.ResteasyNullProvider
|
|
@ -33,8 +33,11 @@ import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RealmSpi;
|
import org.keycloak.models.RealmSpi;
|
||||||
import org.keycloak.models.RoleSpi;
|
import org.keycloak.models.RoleSpi;
|
||||||
|
import org.keycloak.models.UserLoginFailureSpi;
|
||||||
|
import org.keycloak.models.UserSessionSpi;
|
||||||
import org.keycloak.models.UserSpi;
|
import org.keycloak.models.UserSpi;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.models.utils.PostMigrationEvent;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.provider.ProviderManager;
|
import org.keycloak.provider.ProviderManager;
|
||||||
|
@ -67,6 +70,7 @@ import org.junit.Rule;
|
||||||
import org.junit.rules.TestRule;
|
import org.junit.rules.TestRule;
|
||||||
import org.junit.runner.Description;
|
import org.junit.runner.Description;
|
||||||
import org.junit.runners.model.Statement;
|
import org.junit.runners.model.Statement;
|
||||||
|
import org.keycloak.timer.TimerSpi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base of testcases that operate on session level. The tests derived from this class
|
* Base of testcases that operate on session level. The tests derived from this class
|
||||||
|
@ -172,6 +176,9 @@ public abstract class KeycloakModelTest {
|
||||||
.add(RealmSpi.class)
|
.add(RealmSpi.class)
|
||||||
.add(RoleSpi.class)
|
.add(RoleSpi.class)
|
||||||
.add(StoreFactorySpi.class)
|
.add(StoreFactorySpi.class)
|
||||||
|
.add(TimerSpi.class)
|
||||||
|
.add(UserLoginFailureSpi.class)
|
||||||
|
.add(UserSessionSpi.class)
|
||||||
.add(UserSpi.class)
|
.add(UserSpi.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -228,15 +235,15 @@ public abstract class KeycloakModelTest {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
res.init();
|
res.init();
|
||||||
|
res.publish(new PostMigrationEvent());
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reinitializeKeycloakSessionFactory() {
|
public static void reinitializeKeycloakSessionFactory() {
|
||||||
DefaultKeycloakSessionFactory f = createKeycloakSessionFactory();
|
|
||||||
if (FACTORY != null) {
|
if (FACTORY != null) {
|
||||||
FACTORY.close();
|
FACTORY.close();
|
||||||
}
|
}
|
||||||
FACTORY = f;
|
FACTORY = createKeycloakSessionFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 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.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.UserManager;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserProvider;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
|
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProvider;
|
||||||
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.hamcrest.core.Is.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
* @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a>
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
@RequireProvider(UserSessionPersisterProvider.class)
|
||||||
|
@RequireProvider(UserSessionProvider.class)
|
||||||
|
@RequireProvider(UserProvider.class)
|
||||||
|
@RequireProvider(RealmProvider.class)
|
||||||
|
public class UserSessionInitializerTest extends KeycloakModelTest {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().createRealm("test");
|
||||||
|
realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
|
||||||
|
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
realm.setSsoSessionIdleTimeout(1800);
|
||||||
|
realm.setSsoSessionMaxLifespan(36000);
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
|
||||||
|
s.users().addUser(realm, "user1").setEmail("user1@localhost");
|
||||||
|
s.users().addUser(realm, "user2").setEmail("user2@localhost");
|
||||||
|
|
||||||
|
UserSessionPersisterProviderTest.createClients(s, realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().getRealm(realmId);
|
||||||
|
s.sessions().removeUserSessions(realm);
|
||||||
|
|
||||||
|
UserModel user1 = s.users().getUserByUsername(realm, "user1");
|
||||||
|
UserModel user2 = s.users().getUserByUsername(realm, "user2");
|
||||||
|
|
||||||
|
UserManager um = new UserManager(s);
|
||||||
|
if (user1 != null) {
|
||||||
|
um.removeUser(realm, user1);
|
||||||
|
}
|
||||||
|
if (user2 != null) {
|
||||||
|
um.removeUser(realm, user2);
|
||||||
|
}
|
||||||
|
|
||||||
|
s.realms().removeRealm(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserSessionInitializer() {
|
||||||
|
String[] origSessionIds = createSessionsInPersisterOnly();
|
||||||
|
int started = Time.currentTime();
|
||||||
|
|
||||||
|
reinitializeKeycloakSessionFactory();
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
// Assert sessions are in
|
||||||
|
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||||
|
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
||||||
|
|
||||||
|
assertThat("Count of offline sesions for client 'test-app'", session.sessions().getOfflineSessionsCount(realm, testApp), is((long) 3));
|
||||||
|
assertThat("Count of offline sesions for client 'third-party'", session.sessions().getOfflineSessionsCount(realm, thirdparty), is((long) 1));
|
||||||
|
|
||||||
|
List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessionsStream(realm, testApp, 0, 10)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
UserSessionPersisterProviderTest.assertSessions(loadedSessions, origSessionIds);
|
||||||
|
|
||||||
|
assertSessionLoaded(loadedSessions, origSessionIds[0], session.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "test-app", "third-party");
|
||||||
|
assertSessionLoaded(loadedSessions, origSessionIds[1], session.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, started, "test-app");
|
||||||
|
assertSessionLoaded(loadedSessions, origSessionIds[2], session.users().getUserByUsername(realm, "user2"), "127.0.0.3", started, started, "test-app");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserSessionInitializerWithDeletingClient() {
|
||||||
|
String[] origSessionIds = createSessionsInPersisterOnly();
|
||||||
|
int started = Time.currentTime();
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
// Delete one of the clients now
|
||||||
|
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||||
|
realm.removeClient(testApp.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
reinitializeKeycloakSessionFactory();
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
// Assert sessions are in
|
||||||
|
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
||||||
|
|
||||||
|
assertThat("Count of offline sesions for client 'third-party'", session.sessions().getOfflineSessionsCount(realm, thirdparty), is((long) 1));
|
||||||
|
List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessionsStream(realm, thirdparty, 0, 10)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
assertThat("Size of loaded Sessions", loadedSessions.size(), is(1));
|
||||||
|
assertSessionLoaded(loadedSessions, origSessionIds[0], session.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "third-party");
|
||||||
|
|
||||||
|
// Revert client
|
||||||
|
realm.addClient("test-app");
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sessions in persister + infinispan, but then delete them from infinispan cache. This is to allow later testing of initializer. Return the list of "origSessions"
|
||||||
|
private String[] createSessionsInPersisterOnly() {
|
||||||
|
UserSessionModel[] origSessions = inComittedTransaction(session -> { return UserSessionPersisterProviderTest.createSessions(session, realmId); });
|
||||||
|
String[] res = new String[origSessions.length];
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
UserSessionManager sessionManager = new UserSessionManager(session);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (UserSessionModel origSession : origSessions) {
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
|
||||||
|
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
|
||||||
|
sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
|
||||||
|
}
|
||||||
|
String cs = userSession.getNote(UserSessionModel.CORRESPONDING_SESSION_ID);
|
||||||
|
res[i] = cs == null ? userSession.getId() : cs;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
// Delete local user cache (persisted sessions are still kept)
|
||||||
|
UserSessionProvider provider = session.getProvider(UserSessionProvider.class);
|
||||||
|
if (provider instanceof InfinispanUserSessionProvider) {
|
||||||
|
// Remove in-memory representation of the offline sessions
|
||||||
|
((InfinispanUserSessionProvider) provider).removeLocalUserSessions(realm.getId(), true);
|
||||||
|
|
||||||
|
// Clear ispn cache to ensure initializerState is removed as well
|
||||||
|
InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
|
if (infinispan != null) {
|
||||||
|
infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// This is only valid in infinispan provider where the offline session is loaded upon start and never reloaded
|
||||||
|
UserSessionProvider provider = session.getProvider(UserSessionProvider.class);
|
||||||
|
if (provider instanceof InfinispanUserSessionProvider) {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||||
|
ClientModel thirdparty = realm.getClientByClientId("third-party");
|
||||||
|
assertThat("Count of offline sessions for client 'test-app'", session.sessions().getOfflineSessionsCount(realm, testApp), is((long) 0));
|
||||||
|
assertThat("Count of offline sessions for client 'third-party'", session.sessions().getOfflineSessionsCount(realm, thirdparty), is((long) 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
|
||||||
|
for (UserSessionModel session : sessions) {
|
||||||
|
if (session.getId().equals(id)) {
|
||||||
|
UserSessionPersisterProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.fail("Session with ID " + id + " not found in the list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,625 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 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.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.UserManager;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserProvider;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
|
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.services.managers.ClientManager;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
@RequireProvider(UserSessionPersisterProvider.class)
|
||||||
|
@RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID)
|
||||||
|
@RequireProvider(UserProvider.class)
|
||||||
|
@RequireProvider(RealmProvider.class)
|
||||||
|
public class UserSessionPersisterProviderTest extends KeycloakModelTest {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().createRealm("test");
|
||||||
|
realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
|
||||||
|
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
|
||||||
|
s.users().addUser(realm, "user1").setEmail("user1@localhost");
|
||||||
|
s.users().addUser(realm, "user2").setEmail("user2@localhost");
|
||||||
|
|
||||||
|
createClients(s, realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void createClients(KeycloakSession s, RealmModel realm) {
|
||||||
|
ClientModel clientModel = s.clients().addClient(realm, "test-app");
|
||||||
|
clientModel.setEnabled(true);
|
||||||
|
clientModel.setBaseUrl("http://localhost:8180/auth/realms/master/app/auth");
|
||||||
|
Set<String> redirects = new HashSet<>(Arrays.asList("http://localhost:8180/auth/realms/master/app/auth/*",
|
||||||
|
"https://localhost:8543/auth/realms/master/app/auth/*",
|
||||||
|
"http://localhost:8180/auth/realms/test/app/auth/*",
|
||||||
|
"https://localhost:8543/auth/realms/test/app/auth/*"));
|
||||||
|
clientModel.setRedirectUris(redirects);
|
||||||
|
clientModel.setSecret("password");
|
||||||
|
|
||||||
|
clientModel = s.clients().addClient(realm, "third-party");
|
||||||
|
clientModel.setEnabled(true);
|
||||||
|
clientModel.setConsentRequired(true);
|
||||||
|
clientModel.setBaseUrl("http://localhost:8180/auth/realms/master/app/auth");
|
||||||
|
clientModel.setRedirectUris(redirects);
|
||||||
|
clientModel.setSecret("password");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().getRealm(realmId);
|
||||||
|
s.sessions().removeUserSessions(realm);
|
||||||
|
|
||||||
|
UserModel user1 = s.users().getUserByUsername(realm, "user1");
|
||||||
|
UserModel user2 = s.users().getUserByUsername(realm, "user2");
|
||||||
|
|
||||||
|
UserManager um = new UserManager(s);
|
||||||
|
if (user1 != null) {
|
||||||
|
um.removeUser(realm, user1);
|
||||||
|
}
|
||||||
|
if (user2 != null) {
|
||||||
|
um.removeUser(realm, user2);
|
||||||
|
}
|
||||||
|
|
||||||
|
s.realms().removeRealm(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPersistenceWithLoad() {
|
||||||
|
int started = Time.currentTime();
|
||||||
|
final UserSessionModel[] userSession = new UserSessionModel[1];
|
||||||
|
|
||||||
|
UserSessionModel[] origSessions = inComittedTransaction(session -> {
|
||||||
|
// Create some sessions in infinispan
|
||||||
|
return createSessions(session, realmId);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Persist 3 created userSessions and clientSessions as offline
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||||
|
session.sessions().getUserSessionsStream(realm, testApp).collect(Collectors.toList())
|
||||||
|
.forEach(userSessionLooper -> persistUserSession(session, userSessionLooper, true));
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Persist 1 online session
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
userSession[0] = session.sessions().getUserSession(realm, origSessions[0].getId());
|
||||||
|
persistUserSession(session, userSession[0], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> { // Assert online session
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(session, false, 1, 1, 1);
|
||||||
|
assertSession(loadedSessions.get(0), session.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "test-app", "third-party");
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Assert offline sessions
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(session, true, 2, 2, 3);
|
||||||
|
assertSessions(loadedSessions, new String[] { origSessions[0].getId(), origSessions[1].getId(), origSessions[2].getId() });
|
||||||
|
|
||||||
|
assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername(realm, "user1"), "127.0.0.1", started, started, "test-app", "third-party");
|
||||||
|
assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, started, "test-app");
|
||||||
|
assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername(realm, "user2"), "127.0.0.3", started, started, "test-app");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateAndRemove() {
|
||||||
|
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
|
||||||
|
UserSessionModel[] origSessions = createSessions(session, realmId);
|
||||||
|
origSessionsAt.set(origSessions);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
UserSessionModel[] origSessions = origSessionsAt.get();
|
||||||
|
|
||||||
|
// Persist 1 offline session
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
|
||||||
|
userSessionAt.set(userSession);
|
||||||
|
|
||||||
|
persistUserSession(session, userSession, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
// create new clientSession
|
||||||
|
AuthenticatedClientSessionModel clientSession = createClientSession(session, realmId, realm.getClientByClientId("third-party"), session.sessions().getUserSession(realm, persistedSession.getId()),
|
||||||
|
"http://redirect", "state");
|
||||||
|
persister.createClientSession(clientSession, true);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
UserSessionModel userSession = userSessionAt.get();
|
||||||
|
|
||||||
|
// Remove clientSession
|
||||||
|
persister.removeClientSession(userSession.getId(), realm.getClientByClientId("third-party").getId(), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
// Assert clientSession removed
|
||||||
|
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(session, true, 10, 1, 1);
|
||||||
|
UserSessionModel persistedSession = loadedSessions.get(0);
|
||||||
|
assertSession(persistedSession, session.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, started, "test-app");
|
||||||
|
|
||||||
|
// Remove userSession
|
||||||
|
persister.removeUserSession(persistedSession.getId(), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Assert nothing found
|
||||||
|
loadPersistedSessionsPaginated(session, true, 10, 0, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnRealmRemoved() {
|
||||||
|
AtomicReference<String> userSessionID = new AtomicReference<>();
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
|
||||||
|
fooRealm.setDefaultRole(session.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + fooRealm.getName()));
|
||||||
|
|
||||||
|
fooRealm.addClient("foo-app");
|
||||||
|
session.users().addUser(fooRealm, "user3");
|
||||||
|
|
||||||
|
UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername(fooRealm, "user3"), "user3", "127.0.0.1", "form", true, null, null);
|
||||||
|
userSessionID.set(userSession.getId());
|
||||||
|
|
||||||
|
createClientSession(session, realmId, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Persist offline session
|
||||||
|
RealmModel fooRealm = session.realms().getRealm("foo");
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(fooRealm, userSessionID.get());
|
||||||
|
persistUserSession(session, userSession, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Assert session was persisted
|
||||||
|
loadPersistedSessionsPaginated(session, true, 10, 1, 1);
|
||||||
|
|
||||||
|
// Remove realm
|
||||||
|
RealmManager realmMgr = new RealmManager(session);
|
||||||
|
realmMgr.removeRealm(realmMgr.getRealm("foo"));
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Assert nothing loaded
|
||||||
|
loadPersistedSessionsPaginated(session, true, 10, 0, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnClientRemoved() {
|
||||||
|
int started = Time.currentTime();
|
||||||
|
AtomicReference<String> userSessionID = new AtomicReference<>();
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
|
||||||
|
fooRealm.setDefaultRole(session.roles().addRealmRole(fooRealm, Constants.DEFAULT_ROLES_ROLE_PREFIX));
|
||||||
|
|
||||||
|
fooRealm.addClient("foo-app");
|
||||||
|
fooRealm.addClient("bar-app");
|
||||||
|
session.users().addUser(fooRealm, "user3");
|
||||||
|
|
||||||
|
UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername(fooRealm, "user3"), "user3", "127.0.0.1", "form", true, null, null);
|
||||||
|
userSessionID.set(userSession.getId());
|
||||||
|
|
||||||
|
createClientSession(session, realmId, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
|
||||||
|
createClientSession(session, realmId, fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state");
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel fooRealm = session.realms().getRealm("foo");
|
||||||
|
|
||||||
|
// Persist offline session
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(fooRealm, userSessionID.get());
|
||||||
|
persistUserSession(session, userSession, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmManager realmMgr = new RealmManager(session);
|
||||||
|
ClientManager clientMgr = new ClientManager(realmMgr);
|
||||||
|
RealmModel fooRealm = realmMgr.getRealm("foo");
|
||||||
|
|
||||||
|
// Assert session was persisted with both clientSessions
|
||||||
|
UserSessionModel persistedSession = loadPersistedSessionsPaginated(session, true, 10, 1, 1).get(0);
|
||||||
|
assertSession(persistedSession, session.users().getUserByUsername(fooRealm, "user3"), "127.0.0.1", started, started, "foo-app", "bar-app");
|
||||||
|
|
||||||
|
// Remove foo-app client
|
||||||
|
ClientModel client = fooRealm.getClientByClientId("foo-app");
|
||||||
|
clientMgr.removeClient(fooRealm, client);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmManager realmMgr = new RealmManager(session);
|
||||||
|
ClientManager clientMgr = new ClientManager(realmMgr);
|
||||||
|
RealmModel fooRealm = realmMgr.getRealm("foo");
|
||||||
|
|
||||||
|
// Assert just one bar-app clientSession persisted now
|
||||||
|
UserSessionModel persistedSession = loadPersistedSessionsPaginated(session, true, 10, 1, 1).get(0);
|
||||||
|
assertSession(persistedSession, session.users().getUserByUsername(fooRealm, "user3"), "127.0.0.1", started, started, "bar-app");
|
||||||
|
|
||||||
|
// Remove bar-app client
|
||||||
|
ClientModel client = fooRealm.getClientByClientId("bar-app");
|
||||||
|
clientMgr.removeClient(fooRealm, client);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Assert loading still works - last userSession is still there, but no clientSession on it
|
||||||
|
loadPersistedSessionsPaginated(session, true, 10, 1, 1);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
RealmManager realmMgr = new RealmManager(session);
|
||||||
|
realmMgr.removeRealm(realmMgr.getRealm("foo"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnUserRemoved() {
|
||||||
|
int started = Time.currentTime();
|
||||||
|
AtomicReference<UserSessionModel[]> origSessionsAt = new AtomicReference<>();
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Create some sessions in infinispan
|
||||||
|
UserSessionModel[] origSessions = createSessions(session, realmId);
|
||||||
|
origSessionsAt.set(origSessions);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionModel[] origSessions = origSessionsAt.get();
|
||||||
|
|
||||||
|
// Persist 2 offline sessions of 2 users
|
||||||
|
UserSessionModel userSession1 = session.sessions().getUserSession(realm, origSessions[1].getId());
|
||||||
|
UserSessionModel userSession2 = session.sessions().getUserSession(realm, origSessions[2].getId());
|
||||||
|
persistUserSession(session, userSession1, true);
|
||||||
|
persistUserSession(session, userSession2, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
// Load offline sessions
|
||||||
|
loadPersistedSessionsPaginated(session, true, 10, 1, 2);
|
||||||
|
|
||||||
|
// Properly delete user and assert his offlineSession removed
|
||||||
|
UserModel user1 = session.users().getUserByUsername(realm, "user1");
|
||||||
|
new UserManager(session).removeUser(realm, user1);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
Assert.assertEquals(1, persister.getUserSessionsCount(true));
|
||||||
|
|
||||||
|
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(session, true, 10, 1, 1);
|
||||||
|
UserSessionModel persistedSession = loadedSessions.get(0);
|
||||||
|
assertSession(persistedSession, session.users().getUserByUsername(realm, "user2"), "127.0.0.3", started, started, "test-app");
|
||||||
|
|
||||||
|
// KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly".
|
||||||
|
// No exception will happen. However session will be still there
|
||||||
|
UserModel user2 = session.users().getUserByUsername(realm, "user2");
|
||||||
|
session.users().removeUser(realm, user2);
|
||||||
|
|
||||||
|
loadedSessions = loadPersistedSessionsPaginated(session, true, 10, 1, 1);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
UserSessionModel userSession = loadedSessions.get(0);
|
||||||
|
session.sessions().removeUserSession(realm, userSession);
|
||||||
|
persister.removeUserSession(userSession.getId(), userSession.isOffline());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-1999
|
||||||
|
@Test
|
||||||
|
public void testNoSessions() {
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
Stream<UserSessionModel> sessions = persister.loadUserSessionsStream(0, 1, true, 0, "abc");
|
||||||
|
Assert.assertEquals(0, sessions.count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMoreSessions() {
|
||||||
|
AtomicReference<List<UserSessionModel>> userSessionsAt = new AtomicReference<>();
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
// Create 10 userSessions - each having 1 clientSession
|
||||||
|
List<UserSessionModel> userSessions = new ArrayList<>();
|
||||||
|
UserModel user = session.users().getUserByUsername(realm, "user1");
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
// Having different offsets for each session (to ensure that lastSessionRefresh is also different)
|
||||||
|
Time.setOffset(i);
|
||||||
|
|
||||||
|
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");
|
||||||
|
userSessions.add(userSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
userSessionsAt.set(userSessions);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
List<UserSessionModel> userSessions = userSessionsAt.get();
|
||||||
|
|
||||||
|
for (UserSessionModel userSession : userSessions) {
|
||||||
|
UserSessionModel userSession2 = session.sessions().getUserSession(realm, userSession.getId());
|
||||||
|
persistUserSession(session, userSession2, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(session, true, 2, 10, 20);
|
||||||
|
UserModel user = session.users().getUserByUsername(realm, "user1");
|
||||||
|
ClientModel testApp = realm.getClientByClientId("test-app");
|
||||||
|
|
||||||
|
for (UserSessionModel loadedSession : loadedSessions) {
|
||||||
|
assertEquals(user.getId(), loadedSession.getUser().getId());
|
||||||
|
assertEquals("127.0.0.1", loadedSession.getIpAddress());
|
||||||
|
assertEquals(user.getUsername(), loadedSession.getLoginUsername());
|
||||||
|
|
||||||
|
assertEquals(1, loadedSession.getAuthenticatedClientSessions().size());
|
||||||
|
assertTrue(loadedSession.getAuthenticatedClientSessions().containsKey(testApp.getId()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpiredSessions() {
|
||||||
|
int started = Time.currentTime();
|
||||||
|
final UserSessionModel[] userSession1 = {null};
|
||||||
|
final UserSessionModel[] userSession2 = {null};
|
||||||
|
|
||||||
|
UserSessionModel[] origSessions = inComittedTransaction(session -> {
|
||||||
|
// Create some sessions in infinispan
|
||||||
|
return createSessions(session, realmId);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Persist 2 offline sessions of 2 users
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
userSession1[0] = session.sessions().getUserSession(realm, origSessions[1].getId());
|
||||||
|
userSession2[0] = session.sessions().getUserSession(realm, origSessions[2].getId());
|
||||||
|
persistUserSession(session, userSession1[0], true);
|
||||||
|
persistUserSession(session, userSession2[0], true);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
// Update one of the sessions with lastSessionRefresh of 20 days ahead
|
||||||
|
int lastSessionRefresh = Time.currentTime() + 1728000;
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
persister.updateLastSessionRefreshes(realm, lastSessionRefresh, Collections.singleton(userSession1[0].getId()), true);
|
||||||
|
|
||||||
|
// Increase time offset - 40 days
|
||||||
|
Time.setOffset(3456000);
|
||||||
|
try {
|
||||||
|
// Run expiration thread
|
||||||
|
persister.removeExpired(realm);
|
||||||
|
|
||||||
|
// Test the updated session is still in persister. Not updated session is not there anymore
|
||||||
|
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(session, true, 10, 1, 1);
|
||||||
|
UserSessionModel persistedSession = loadedSessions.get(0);
|
||||||
|
assertSession(persistedSession, session.users().getUserByUsername(realm, "user1"), "127.0.0.2", started, lastSessionRefresh, "test-app");
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// Cleanup
|
||||||
|
Time.setOffset(0);
|
||||||
|
session.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static AuthenticatedClientSessionModel createClientSession(KeycloakSession session, String realmId, ClientModel client, UserSessionModel userSession, String redirect, String state) {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
|
||||||
|
clientSession.setRedirectUri(redirect);
|
||||||
|
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
|
||||||
|
return clientSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static UserSessionModel[] createSessions(KeycloakSession session, String realmId) {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
UserSessionModel[] sessions = new UserSessionModel[3];
|
||||||
|
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null);
|
||||||
|
|
||||||
|
createClientSession(session, realmId, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state");
|
||||||
|
createClientSession(session, realmId, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state");
|
||||||
|
|
||||||
|
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.2", "form", true, null, null);
|
||||||
|
createClientSession(session, realmId, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state");
|
||||||
|
|
||||||
|
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername(realm, "user2"), "user2", "127.0.0.3", "form", true, null, null);
|
||||||
|
createClientSession(session, realmId, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state");
|
||||||
|
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistUserSession(KeycloakSession session, UserSessionModel userSession, boolean offline) {
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
persister.createUserSession(userSession, offline);
|
||||||
|
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
|
||||||
|
persister.createClientSession(clientSession, offline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
|
||||||
|
for (UserSessionModel session : sessions) {
|
||||||
|
if (session.getId().equals(id)) {
|
||||||
|
assertSession(session, user, ipAddress, started, lastRefresh, clients);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.fail("Session with ID " + id + " not found in the list");
|
||||||
|
}
|
||||||
|
|
||||||
|
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<>();
|
||||||
|
int lastCreatedOn = 0;
|
||||||
|
String lastSessionId = "abc";
|
||||||
|
|
||||||
|
while (next) {
|
||||||
|
List<UserSessionModel> sess = persister
|
||||||
|
.loadUserSessionsStream(0, sessionsPerPage, offline, lastCreatedOn, lastSessionId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (sess.size() < sessionsPerPage) {
|
||||||
|
next = false;
|
||||||
|
|
||||||
|
// We had at least some session
|
||||||
|
if (sess.size() > 0) {
|
||||||
|
pageCount++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pageCount++;
|
||||||
|
|
||||||
|
UserSessionModel lastSession = sess.get(sess.size() - 1);
|
||||||
|
lastCreatedOn = lastSession.getStarted();
|
||||||
|
lastSessionId = lastSession.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.addAll(sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedPageCount, pageCount);
|
||||||
|
Assert.assertEquals(expectedSessionsCount, result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertSession(UserSessionModel session, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
|
||||||
|
assertEquals(user.getId(), session.getUser().getId());
|
||||||
|
assertEquals(ipAddress, session.getIpAddress());
|
||||||
|
assertEquals(user.getUsername(), session.getLoginUsername());
|
||||||
|
assertEquals("form", session.getAuthMethod());
|
||||||
|
assertTrue(session.isRememberMe());
|
||||||
|
assertTrue(session.getStarted() >= started - 1 && session.getStarted() <= started + 1);
|
||||||
|
assertTrue(session.getLastSessionRefresh() >= lastRefresh - 1 && session.getLastSessionRefresh() <= lastRefresh + 1);
|
||||||
|
|
||||||
|
String[] actualClients = new String[session.getAuthenticatedClientSessions().size()];
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : session.getAuthenticatedClientSessions().entrySet()) {
|
||||||
|
String clientUUID = entry.getKey();
|
||||||
|
AuthenticatedClientSessionModel clientSession = entry.getValue();
|
||||||
|
Assert.assertEquals(clientUUID, clientSession.getClient().getId());
|
||||||
|
actualClients[i] = clientSession.getClient().getClientId();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(actualClients, Matchers.arrayContainingInAnyOrder(clients));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertSessions(List<UserSessionModel> actualSessions, String[] expectedSessionIds) {
|
||||||
|
String[] actual = new String[actualSessions.size()];
|
||||||
|
for (int i = 0; i < actual.length; i++) {
|
||||||
|
actual[i] = actualSessions.get(i).getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(actual, Matchers.arrayContainingInAnyOrder(expectedSessionIds));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.UserManager;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserProvider;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStoreFactory;
|
||||||
|
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
||||||
|
import org.keycloak.testsuite.model.infinispan.InfinispanTestUtil;
|
||||||
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.model.UserSessionPersisterProviderTest.createClients;
|
||||||
|
import static org.keycloak.testsuite.model.UserSessionPersisterProviderTest.createSessions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
@RequireProvider(UserSessionPersisterProvider.class)
|
||||||
|
@RequireProvider(UserSessionProvider.class)
|
||||||
|
@RequireProvider(UserProvider.class)
|
||||||
|
@RequireProvider(RealmProvider.class)
|
||||||
|
public class UserSessionProviderModelTest extends KeycloakModelTest {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
private KeycloakSession kcSession;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().createRealm("test");
|
||||||
|
realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
|
||||||
|
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
realm.setSsoSessionIdleTimeout(1800);
|
||||||
|
realm.setSsoSessionMaxLifespan(36000);
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
this.kcSession = s;
|
||||||
|
|
||||||
|
s.users().addUser(realm, "user1").setEmail("user1@localhost");
|
||||||
|
s.users().addUser(realm, "user2").setEmail("user2@localhost");
|
||||||
|
|
||||||
|
createClients(s, realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().getRealm(realmId);
|
||||||
|
s.sessions().removeUserSessions(realm);
|
||||||
|
|
||||||
|
UserModel user1 = s.users().getUserByUsername(realm, "user1");
|
||||||
|
UserModel user2 = s.users().getUserByUsername(realm, "user2");
|
||||||
|
|
||||||
|
UserManager um = new UserManager(s);
|
||||||
|
if (user1 != null) {
|
||||||
|
um.removeUser(realm, user1);
|
||||||
|
}
|
||||||
|
if (user2 != null) {
|
||||||
|
um.removeUser(realm, user2);
|
||||||
|
}
|
||||||
|
|
||||||
|
s.realms().removeRealm(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleSessionsRemovalInOneTransaction() {
|
||||||
|
UserSessionModel[] origSessions = inComittedTransaction(session -> { return createSessions(session, realmId); });
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
|
||||||
|
Assert.assertEquals(origSessions[0], userSession);
|
||||||
|
|
||||||
|
userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
|
||||||
|
Assert.assertEquals(origSessions[1], userSession);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
session.sessions().removeUserSession(realm, origSessions[0]);
|
||||||
|
session.sessions().removeUserSession(realm, origSessions[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
|
||||||
|
Assert.assertNull(userSession);
|
||||||
|
|
||||||
|
userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
|
||||||
|
Assert.assertNull(userSession);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpiredClientSessions() {
|
||||||
|
// Suspend periodic tasks to avoid race-conditions, which may cause missing updates of lastSessionRefresh times to UserSessionPersisterProvider
|
||||||
|
TimerProvider timer = kcSession.getProvider(TimerProvider.class);
|
||||||
|
TimerProvider.TimerTaskContext timerTaskCtx = null;
|
||||||
|
if (timer != null) {
|
||||||
|
timerTaskCtx = timer.cancelTask(PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
log.info("Cancelled periodic task " + PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
|
||||||
|
InfinispanTestUtil.setTestingTimeService(kcSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicReference<List<String>> clientSessionIds = new AtomicReference<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
UserSessionModel[] origSessions = inComittedTransaction(session -> {
|
||||||
|
// create some user and client sessions
|
||||||
|
return createSessions(session, realmId);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
|
||||||
|
Assert.assertEquals(origSessions[0], userSession);
|
||||||
|
|
||||||
|
AuthenticatedClientSessionModel clientSession = session.sessions().getClientSession(userSession, realm.getClientByClientId("test-app"),
|
||||||
|
UUID.fromString(origSessions[0].getAuthenticatedClientSessionByClient(realm.getClientByClientId("test-app").getId()).getId()),
|
||||||
|
false);
|
||||||
|
Assert.assertEquals(origSessions[0].getAuthenticatedClientSessionByClient(realm.getClientByClientId("test-app").getId()).getId(), clientSession.getId());
|
||||||
|
|
||||||
|
userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
|
||||||
|
Assert.assertEquals(origSessions[1], userSession);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
|
||||||
|
|
||||||
|
Collection<AuthenticatedClientSessionModel> values = userSession.getAuthenticatedClientSessions().values();
|
||||||
|
List<String> clientSessions = new LinkedList<>();
|
||||||
|
values.stream().forEach(clientSession -> {
|
||||||
|
// expire client sessions
|
||||||
|
clientSession.setTimestamp(1);
|
||||||
|
clientSessions.add(clientSession.getId());
|
||||||
|
});
|
||||||
|
clientSessionIds.set(clientSessions);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
// assert the user session is still there
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
|
||||||
|
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"), UUID.fromString(clientSessionId), false)));
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Time.setOffset(0);
|
||||||
|
kcSession.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
||||||
|
if (timer != null && timerTaskCtx != null) {
|
||||||
|
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
|
||||||
|
InfinispanTestUtil.revertTimeService(kcSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.UserManager;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserProvider;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStoreFactory;
|
||||||
|
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
||||||
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
|
import org.keycloak.testsuite.model.infinispan.InfinispanTestUtil;
|
||||||
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
*/
|
||||||
|
@RequireProvider(UserSessionPersisterProvider.class)
|
||||||
|
@RequireProvider(value=UserSessionProvider.class, only={"infinispan"})
|
||||||
|
@RequireProvider(UserProvider.class)
|
||||||
|
@RequireProvider(RealmProvider.class)
|
||||||
|
public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
private KeycloakSession kcSession;
|
||||||
|
|
||||||
|
private UserSessionManager sessionManager;
|
||||||
|
private UserSessionPersisterProvider persister;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().createRealm("test");
|
||||||
|
realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
|
||||||
|
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
this.kcSession = s;
|
||||||
|
|
||||||
|
s.users().addUser(realm, "user1").setEmail("user1@localhost");
|
||||||
|
s.users().addUser(realm, "user2").setEmail("user2@localhost");
|
||||||
|
|
||||||
|
UserSessionPersisterProviderTest.createClients(s, realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().getRealm(realmId);
|
||||||
|
s.sessions().removeUserSessions(realm);
|
||||||
|
|
||||||
|
UserModel user1 = s.users().getUserByUsername(realm, "user1");
|
||||||
|
UserModel user2 = s.users().getUserByUsername(realm, "user2");
|
||||||
|
|
||||||
|
UserManager um = new UserManager(s);
|
||||||
|
if (user1 != null) {
|
||||||
|
um.removeUser(realm, user1);
|
||||||
|
}
|
||||||
|
if (user2 != null) {
|
||||||
|
um.removeUser(realm, user2);
|
||||||
|
}
|
||||||
|
|
||||||
|
s.realms().removeRealm(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpired() {
|
||||||
|
// Suspend periodic tasks to avoid race-conditions, which may cause missing updates of lastSessionRefresh times to UserSessionPersisterProvider
|
||||||
|
TimerProvider timer = kcSession.getProvider(TimerProvider.class);
|
||||||
|
TimerProvider.TimerTaskContext timerTaskCtx = null;
|
||||||
|
if (timer != null) {
|
||||||
|
timerTaskCtx = timer.cancelTask(PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
log.info("Cancelled periodic task " + PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
InfinispanTestUtil.setTestingTimeService(kcSession);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Key is userSessionId, value is set of client UUIDS
|
||||||
|
Map<String, Set<String>> offlineSessions = new HashMap<>();
|
||||||
|
ClientModel[] testApp = new ClientModel[1];
|
||||||
|
|
||||||
|
UserSessionModel[] origSessions = inComittedTransaction(session -> {
|
||||||
|
// Create some online sessions in infinispan
|
||||||
|
return UserSessionPersisterProviderTest.createSessions(session, realmId);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
sessionManager = new UserSessionManager(session);
|
||||||
|
persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
// Persist 3 created userSessions and clientSessions as offline
|
||||||
|
testApp[0] = realm.getClientByClientId("test-app");
|
||||||
|
session.sessions().getUserSessionsStream(realm, testApp[0]).collect(Collectors.toList())
|
||||||
|
.forEach(userSession -> offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(session, userSession)));
|
||||||
|
|
||||||
|
// Assert all previously saved offline sessions found
|
||||||
|
for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
|
||||||
|
UserSessionModel foundSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
|
||||||
|
Assert.assertEquals(foundSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("Persisted 3 sessions to UserSessionPersisterProvider");
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
|
||||||
|
Assert.assertNotNull(session0);
|
||||||
|
|
||||||
|
// sessions are in persister too
|
||||||
|
Assert.assertEquals(3, persister.getUserSessionsCount(true));
|
||||||
|
|
||||||
|
Time.setOffset(300);
|
||||||
|
log.infof("Set time offset to 300. Time is: %d", Time.currentTime());
|
||||||
|
|
||||||
|
// Set lastSessionRefresh to currentSession[0] to 0
|
||||||
|
session0.setLastSessionRefresh(Time.currentTime());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Increase timeOffset and update LSR of the session two times - first to 20 days and then to 21 days. At least one of updates
|
||||||
|
// will propagate to PersisterLastSessionRefreshStore and update DB (Single update is not 100% sure as there is still a
|
||||||
|
// chance of delayed periodic task to be run in the meantime and causing race-condition, which would mean LSR not updated in the DB)
|
||||||
|
IntStream.range(0, 2).sequential().forEach(index -> inComittedTransaction(index, (session, i) -> {
|
||||||
|
int timeOffset = 1728000 + (i * 86400);
|
||||||
|
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
Time.setOffset(timeOffset);
|
||||||
|
log.infof("Set time offset to %d. Time is: %d", timeOffset, Time.currentTime());
|
||||||
|
|
||||||
|
UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
|
||||||
|
session0.setLastSessionRefresh(Time.currentTime());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
// Increase timeOffset - 40 days
|
||||||
|
Time.setOffset(3456000);
|
||||||
|
log.infof("Set time offset to 3456000. Time is: %d", Time.currentTime());
|
||||||
|
|
||||||
|
// Expire and ensure that all sessions despite session0 were removed
|
||||||
|
persister.removeExpired(realm);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
// assert session0 is the only session found
|
||||||
|
Assert.assertNotNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
|
||||||
|
Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[1].getId()));
|
||||||
|
Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[2].getId()));
|
||||||
|
|
||||||
|
Assert.assertEquals(1, persister.getUserSessionsCount(true));
|
||||||
|
|
||||||
|
// Expire everything and assert nothing found
|
||||||
|
Time.setOffset(7000000);
|
||||||
|
|
||||||
|
persister.removeExpired(realm);
|
||||||
|
});
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
sessionManager = new UserSessionManager(session);
|
||||||
|
persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
|
||||||
|
for (String userSessionId : offlineSessions.keySet()) {
|
||||||
|
Assert.assertNull(sessionManager.findOfflineUserSession(realm, userSessionId));
|
||||||
|
}
|
||||||
|
Assert.assertEquals(0, persister.getUserSessionsCount(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
Time.setOffset(0);
|
||||||
|
kcSession.getKeycloakSessionFactory().publish(new ResetTimeOffsetEvent());
|
||||||
|
if (timer != null) {
|
||||||
|
timer.schedule(timerTaskCtx.getRunnable(), timerTaskCtx.getIntervalMillis(), PersisterLastSessionRefreshStoreFactory.DB_LSR_PERIODIC_TASK_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
InfinispanTestUtil.revertTimeService(kcSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> createOfflineSessionIncludeClientSessions(KeycloakSession session, UserSessionModel
|
||||||
|
userSession) {
|
||||||
|
Set<String> offlineSessions = new HashSet<>();
|
||||||
|
UserSessionManager localManager = new UserSessionManager(session);
|
||||||
|
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
|
||||||
|
localManager.createOrUpdateOfflineSession(clientSession, userSession);
|
||||||
|
offlineSessions.add(clientSession.getClient().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return offlineSessions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,11 @@ package org.keycloak.testsuite.model.parameters;
|
||||||
import org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory;
|
import org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
|
import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterSpi;
|
||||||
|
import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory;
|
||||||
|
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
||||||
|
import org.keycloak.sessions.StickySessionEncoderProviderFactory;
|
||||||
|
import org.keycloak.sessions.StickySessionEncoderSpi;
|
||||||
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
||||||
import org.keycloak.models.cache.CacheRealmProviderSpi;
|
import org.keycloak.models.cache.CacheRealmProviderSpi;
|
||||||
import org.keycloak.models.cache.CacheUserProviderSpi;
|
import org.keycloak.models.cache.CacheUserProviderSpi;
|
||||||
|
@ -28,6 +33,8 @@ import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.provider.Spi;
|
import org.keycloak.provider.Spi;
|
||||||
import org.keycloak.testsuite.model.Config;
|
import org.keycloak.testsuite.model.Config;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import org.keycloak.timer.TimerProviderFactory;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,6 +47,8 @@ public class Infinispan extends KeycloakModelParameters {
|
||||||
.add(CacheRealmProviderSpi.class)
|
.add(CacheRealmProviderSpi.class)
|
||||||
.add(CacheUserProviderSpi.class)
|
.add(CacheUserProviderSpi.class)
|
||||||
.add(InfinispanConnectionSpi.class)
|
.add(InfinispanConnectionSpi.class)
|
||||||
|
.add(StickySessionEncoderSpi.class)
|
||||||
|
.add(UserSessionPersisterSpi.class)
|
||||||
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -48,6 +57,10 @@ public class Infinispan extends KeycloakModelParameters {
|
||||||
.add(InfinispanClusterProviderFactory.class)
|
.add(InfinispanClusterProviderFactory.class)
|
||||||
.add(InfinispanConnectionProviderFactory.class)
|
.add(InfinispanConnectionProviderFactory.class)
|
||||||
.add(InfinispanUserCacheProviderFactory.class)
|
.add(InfinispanUserCacheProviderFactory.class)
|
||||||
|
.add(InfinispanUserSessionProviderFactory.class)
|
||||||
|
.add(InfinispanUserLoginFailureProviderFactory.class)
|
||||||
|
.add(StickySessionEncoderProviderFactory.class)
|
||||||
|
.add(TimerProviderFactory.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -25,6 +25,8 @@ import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionPr
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionSpi;
|
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionSpi;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.lock.LiquibaseDBLockProviderFactory;
|
import org.keycloak.connections.jpa.updater.liquibase.lock.LiquibaseDBLockProviderFactory;
|
||||||
import org.keycloak.events.jpa.JpaEventStoreProviderFactory;
|
import org.keycloak.events.jpa.JpaEventStoreProviderFactory;
|
||||||
|
import org.keycloak.models.jpa.session.JpaUserSessionPersisterProviderFactory;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterSpi;
|
||||||
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
||||||
import org.keycloak.models.dblock.DBLockSpi;
|
import org.keycloak.models.dblock.DBLockSpi;
|
||||||
import org.keycloak.models.jpa.JpaClientProviderFactory;
|
import org.keycloak.models.jpa.JpaClientProviderFactory;
|
||||||
|
@ -51,6 +53,7 @@ public class Jpa extends KeycloakModelParameters {
|
||||||
.add(JpaConnectionSpi.class)
|
.add(JpaConnectionSpi.class)
|
||||||
.add(JpaUpdaterSpi.class)
|
.add(JpaUpdaterSpi.class)
|
||||||
.add(LiquibaseConnectionSpi.class)
|
.add(LiquibaseConnectionSpi.class)
|
||||||
|
.add(UserSessionPersisterSpi.class)
|
||||||
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -68,6 +71,7 @@ public class Jpa extends KeycloakModelParameters {
|
||||||
.add(JpaUserProviderFactory.class)
|
.add(JpaUserProviderFactory.class)
|
||||||
.add(LiquibaseConnectionProviderFactory.class)
|
.add(LiquibaseConnectionProviderFactory.class)
|
||||||
.add(LiquibaseDBLockProviderFactory.class)
|
.add(LiquibaseDBLockProviderFactory.class)
|
||||||
|
.add(JpaUserSessionPersisterProviderFactory.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public Jpa() {
|
public Jpa() {
|
||||||
|
|
|
@ -16,10 +16,15 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.model.parameters;
|
package org.keycloak.testsuite.model.parameters;
|
||||||
|
|
||||||
|
import org.keycloak.models.UserLoginFailureSpi;
|
||||||
|
import org.keycloak.models.UserSessionSpi;
|
||||||
|
import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
|
||||||
|
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
||||||
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
||||||
import org.keycloak.models.map.client.MapClientProviderFactory;
|
import org.keycloak.models.map.client.MapClientProviderFactory;
|
||||||
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
|
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
|
||||||
import org.keycloak.models.map.group.MapGroupProviderFactory;
|
import org.keycloak.models.map.group.MapGroupProviderFactory;
|
||||||
|
import org.keycloak.models.map.realm.MapRealmProviderFactory;
|
||||||
import org.keycloak.models.map.role.MapRoleProviderFactory;
|
import org.keycloak.models.map.role.MapRoleProviderFactory;
|
||||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
import org.keycloak.models.map.storage.MapStorageSpi;
|
import org.keycloak.models.map.storage.MapStorageSpi;
|
||||||
|
@ -45,9 +50,12 @@ public class Map extends KeycloakModelParameters {
|
||||||
.add(MapClientProviderFactory.class)
|
.add(MapClientProviderFactory.class)
|
||||||
.add(MapClientScopeProviderFactory.class)
|
.add(MapClientScopeProviderFactory.class)
|
||||||
.add(MapGroupProviderFactory.class)
|
.add(MapGroupProviderFactory.class)
|
||||||
|
.add(MapRealmProviderFactory.class)
|
||||||
.add(MapRoleProviderFactory.class)
|
.add(MapRoleProviderFactory.class)
|
||||||
.add(MapUserProviderFactory.class)
|
.add(MapUserProviderFactory.class)
|
||||||
.add(MapStorageProvider.class)
|
.add(MapStorageProvider.class)
|
||||||
|
.add(MapUserSessionProviderFactory.class)
|
||||||
|
.add(MapUserLoginFailureProviderFactory.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public Map() {
|
public Map() {
|
||||||
|
@ -59,8 +67,11 @@ public class Map extends KeycloakModelParameters {
|
||||||
cf.spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID)
|
cf.spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID)
|
||||||
.spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID)
|
.spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID)
|
||||||
.spi("group").defaultProvider(MapGroupProviderFactory.PROVIDER_ID)
|
.spi("group").defaultProvider(MapGroupProviderFactory.PROVIDER_ID)
|
||||||
|
.spi("realm").defaultProvider(MapRealmProviderFactory.PROVIDER_ID)
|
||||||
.spi("role").defaultProvider(MapRoleProviderFactory.PROVIDER_ID)
|
.spi("role").defaultProvider(MapRoleProviderFactory.PROVIDER_ID)
|
||||||
.spi("user").defaultProvider(MapUserProviderFactory.PROVIDER_ID)
|
.spi("user").defaultProvider(MapUserProviderFactory.PROVIDER_ID)
|
||||||
|
.spi(UserSessionSpi.NAME).defaultProvider(MapUserSessionProviderFactory.PROVIDER_ID)
|
||||||
|
.spi(UserLoginFailureSpi.NAME).defaultProvider(MapUserLoginFailureProviderFactory.PROVIDER_ID)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ log4j.rootLogger=info, keycloak
|
||||||
|
|
||||||
log4j.appender.keycloak=org.apache.log4j.ConsoleAppender
|
log4j.appender.keycloak=org.apache.log4j.ConsoleAppender
|
||||||
log4j.appender.keycloak.layout=org.apache.log4j.EnhancedPatternLayout
|
log4j.appender.keycloak.layout=org.apache.log4j.EnhancedPatternLayout
|
||||||
keycloak.testsuite.logging.pattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
|
keycloak.testsuite.logging.pattern=%d{HH:mm:ss,SSS} %-5p [%c] (%t) %m%n
|
||||||
log4j.appender.keycloak.layout.ConversionPattern=${keycloak.testsuite.logging.pattern}
|
log4j.appender.keycloak.layout.ConversionPattern=${keycloak.testsuite.logging.pattern}
|
||||||
|
|
||||||
# Logging with "info" when running test from IDE, but disabled when running test with "mvn" . Both cases can be overriden by use system property "keycloak.logging.level" (eg. -Dkeycloak.logging.level=debug )
|
# Logging with "info" when running test from IDE, but disabled when running test with "mvn" . Both cases can be overriden by use system property "keycloak.logging.level" (eg. -Dkeycloak.logging.level=debug )
|
||||||
|
@ -42,4 +42,7 @@ log4j.logger.org.keycloak.connections.jpa.updater.liquibase=${keycloak.liquibase
|
||||||
log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=debug
|
log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=debug
|
||||||
|
|
||||||
# Enable to log short stack traces for log entries enabled with StackUtil.getShortStackTrace() calls
|
# Enable to log short stack traces for log entries enabled with StackUtil.getShortStackTrace() calls
|
||||||
# log4j.logger.org.keycloak.STACK_TRACE=trace
|
#log4j.logger.org.keycloak.models.map=trace
|
||||||
|
#log4j.logger.org.keycloak.models.map.transaction=debug
|
||||||
|
#
|
||||||
|
#log4j.logger.org.keycloak.STACK_TRACE=trace
|
||||||
|
|
|
@ -38,6 +38,14 @@
|
||||||
"provider": "${keycloak.authSession.provider:infinispan}"
|
"provider": "${keycloak.authSession.provider:infinispan}"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"userSessions": {
|
||||||
|
"provider": "${keycloak.userSession.provider:infinispan}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"loginFailure": {
|
||||||
|
"provider": "${keycloak.loginFailure.provider:infinispan}"
|
||||||
|
},
|
||||||
|
|
||||||
"mapStorage": {
|
"mapStorage": {
|
||||||
"provider": "${keycloak.mapStorage.provider:concurrenthashmap}",
|
"provider": "${keycloak.mapStorage.provider:concurrenthashmap}",
|
||||||
"concurrenthashmap": {
|
"concurrenthashmap": {
|
||||||
|
|
Loading…
Reference in a new issue