diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java index 1853ad1790..3e20e5ffd8 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java @@ -291,4 +291,18 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel update(); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || !(o instanceof AuthenticationSessionModel)) return false; + + AuthenticationSessionModel that = (AuthenticationSessionModel) o; + return that.getTabId().equals(getTabId()); + } + + @Override + public int hashCode() { + return getTabId().hashCode(); + } + } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java index 2e2b10ea3a..9e370c108b 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java @@ -51,13 +51,16 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe private final KeycloakSession session; private final Cache cache; private final InfinispanKeyGenerator keyGenerator; + private final int authSessionsLimit; protected final InfinispanKeycloakTransaction tx; protected final SessionEventsSenderTransaction clusterEventsSenderTx; - public InfinispanAuthenticationSessionProvider(KeycloakSession session, InfinispanKeyGenerator keyGenerator, Cache cache) { + public InfinispanAuthenticationSessionProvider(KeycloakSession session, InfinispanKeyGenerator keyGenerator, + Cache cache, int authSessionsLimit) { this.session = session; this.cache = cache; this.keyGenerator = keyGenerator; + this.authSessionsLimit = authSessionsLimit; this.tx = new InfinispanKeycloakTransaction(); this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session); @@ -88,7 +91,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe private RootAuthenticationSessionAdapter wrap(RealmModel realm, RootAuthenticationSessionEntity entity) { - return entity==null ? null : new RootAuthenticationSessionAdapter(session, this, cache, realm, entity); + return entity==null ? null : new RootAuthenticationSessionAdapter(session, this, cache, realm, entity, authSessionsLimit); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java index 2ddd0a4144..559a90d351 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java @@ -54,8 +54,14 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic private volatile Cache authSessionsCache; + private int authSessionsLimit; + public static final String PROVIDER_ID = "infinispan"; + public static final String AUTH_SESSIONS_LIMIT = "authSessionsLimit"; + + public static final int DEFAULT_AUTH_SESSIONS_LIMIT = 300; + public static final String AUTHENTICATION_SESSION_EVENTS = "AUTHENTICATION_SESSION_EVENTS"; public static final String REALM_REMOVED_AUTHSESSION_EVENT = "REALM_REMOVED_EVENT_AUTHSESSIONS"; @@ -64,7 +70,10 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic @Override public void init(Config.Scope config) { - + // get auth sessions limit from config or use default if not provided + int configInt = config.getInt(AUTH_SESSIONS_LIMIT, DEFAULT_AUTH_SESSIONS_LIMIT); + // use default if provided value is not a positive number + authSessionsLimit = (configInt <= 0) ? DEFAULT_AUTH_SESSIONS_LIMIT : configInt; } @@ -115,7 +124,7 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic @Override public AuthenticationSessionProvider create(KeycloakSession session) { lazyInit(session); - return new InfinispanAuthenticationSessionProvider(session, keyGenerator, authSessionsCache); + return new InfinispanAuthenticationSessionProvider(session, keyGenerator, authSessionsCache, authSessionsLimit); } private void updateAuthNotes(ClusterEvent clEvent) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/RootAuthenticationSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/RootAuthenticationSessionAdapter.java index e55034eac7..3ee2c7406d 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/RootAuthenticationSessionAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/RootAuthenticationSessionAdapter.java @@ -17,11 +17,13 @@ package org.keycloak.models.sessions.infinispan; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.infinispan.Cache; +import org.jboss.logging.Logger; import org.keycloak.common.util.Time; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; @@ -37,20 +39,26 @@ import org.keycloak.sessions.RootAuthenticationSessionModel; */ public class RootAuthenticationSessionAdapter implements RootAuthenticationSessionModel { + private static final Logger log = Logger.getLogger(RootAuthenticationSessionAdapter.class); + private KeycloakSession session; private InfinispanAuthenticationSessionProvider provider; private Cache cache; private RealmModel realm; private RootAuthenticationSessionEntity entity; + private final int authSessionsLimit; + private static Comparator> TIMESTAMP_COMPARATOR = + Comparator.comparingInt(e -> e.getValue().getTimestamp()); public RootAuthenticationSessionAdapter(KeycloakSession session, InfinispanAuthenticationSessionProvider provider, Cache cache, RealmModel realm, - RootAuthenticationSessionEntity entity) { + RootAuthenticationSessionEntity entity, int authSessionsLimt) { this.session = session; this.provider = provider; this.cache = cache; this.realm = realm; this.entity = entity; + this.authSessionsLimit = authSessionsLimt; } void update() { @@ -109,14 +117,29 @@ public class RootAuthenticationSessionAdapter implements RootAuthenticationSessi @Override public AuthenticationSessionModel createAuthenticationSession(ClientModel client) { + Map authenticationSessions = entity.getAuthenticationSessions(); + if (authenticationSessions.size() >= authSessionsLimit) { + String tabId = authenticationSessions.entrySet().stream().min(TIMESTAMP_COMPARATOR).map(Map.Entry::getKey).orElse(null); + + if (tabId != null) { + log.debugf("Reached limit (%s) of active authentication sessions per a root authentication session. Removing oldest authentication session with TabId %s.", authSessionsLimit, tabId); + + // remove the oldest authentication session + authenticationSessions.remove(tabId); + } + } + AuthenticationSessionEntity authSessionEntity = new AuthenticationSessionEntity(); authSessionEntity.setClientUUID(client.getId()); + int timestamp = Time.currentTime(); + authSessionEntity.setTimestamp(timestamp); + String tabId = provider.generateTabId(); - entity.getAuthenticationSessions().put(tabId, authSessionEntity); + authenticationSessions.put(tabId, authSessionEntity); // Update our timestamp when adding new authenticationSession - entity.setTimestamp(Time.currentTime()); + entity.setTimestamp(timestamp); update(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java index 4fe17fda0f..22f5f647ec 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java @@ -42,6 +42,8 @@ public class AuthenticationSessionEntity implements Serializable { private String authUserId; + private int timestamp; + private String redirectUri; private String action; private Set clientScopes; @@ -60,9 +62,20 @@ public class AuthenticationSessionEntity implements Serializable { public AuthenticationSessionEntity( String clientUUID, String authUserId, + int timestamp, String redirectUri, String action, Set clientScopes, Map executionStatus, String protocol, Map clientNotes, Map authNotes, Set requiredActions, Map userSessionNotes) { + this(clientUUID, authUserId, redirectUri, action, clientScopes, executionStatus, protocol, clientNotes, authNotes, requiredActions, userSessionNotes); + this.timestamp = timestamp; + } + + public AuthenticationSessionEntity( + String clientUUID, + String authUserId, + String redirectUri, String action, Set clientScopes, + Map executionStatus, String protocol, + Map clientNotes, Map authNotes, Set requiredActions, Map userSessionNotes) { this.clientUUID = clientUUID; this.authUserId = authUserId; @@ -96,6 +109,14 @@ public class AuthenticationSessionEntity implements Serializable { this.authUserId = authUserId; } + public int getTimestamp() { + return timestamp; + } + + public void setTimestamp(int timestamp) { + this.timestamp = timestamp; + } + public String getRedirectUri() { return redirectUri; } @@ -171,6 +192,7 @@ public class AuthenticationSessionEntity implements Serializable { public static class ExternalizerImpl implements Externalizer { private static final int VERSION_1 = 1; + private static final int VERSION_2 = 2; public static final ExternalizerImpl INSTANCE = new ExternalizerImpl(); @@ -196,12 +218,14 @@ public class AuthenticationSessionEntity implements Serializable { @Override public void writeObject(ObjectOutput output, AuthenticationSessionEntity value) throws IOException { - output.writeByte(VERSION_1); + output.writeByte(VERSION_2); MarshallUtil.marshallString(value.clientUUID, output); MarshallUtil.marshallString(value.authUserId, output); + output.writeInt(value.timestamp); + MarshallUtil.marshallString(value.redirectUri, output); MarshallUtil.marshallString(value.action, output); KeycloakMarshallUtil.writeCollection(value.clientScopes, KeycloakMarshallUtil.STRING_EXT, output); @@ -220,6 +244,8 @@ public class AuthenticationSessionEntity implements Serializable { switch (input.readByte()) { case VERSION_1: return readObjectVersion1(input); + case VERSION_2: + return readObjectVersion2(input); default: throw new IOException("Unknown version"); } @@ -244,5 +270,27 @@ public class AuthenticationSessionEntity implements Serializable { KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)) // userSessionNotes ); } + + public AuthenticationSessionEntity readObjectVersion2(ObjectInput input) throws IOException, ClassNotFoundException { + return new AuthenticationSessionEntity( + MarshallUtil.unmarshallString(input), // clientUUID + + MarshallUtil.unmarshallString(input), // authUserId + + input.readInt(), // timestamp + + MarshallUtil.unmarshallString(input), // redirectUri + MarshallUtil.unmarshallString(input), // action + KeycloakMarshallUtil.readCollection(input, KeycloakMarshallUtil.STRING_EXT, ConcurrentHashMap::newKeySet), // clientScopes + + KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, EXECUTION_STATUS_EXT, size -> new ConcurrentHashMap<>(size)), // executionStatus + MarshallUtil.unmarshallString(input), // protocol + + KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)), // clientNotes + KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)), // authNotes + KeycloakMarshallUtil.readCollection(input, KeycloakMarshallUtil.STRING_EXT, ConcurrentHashMap::newKeySet), // requiredActions + KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)) // userSessionNotes + ); + } } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionEntity.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionEntity.java index fdcbc81c9b..eb47860542 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authSession/MapAuthenticationSessionEntity.java @@ -32,6 +32,8 @@ public class MapAuthenticationSessionEntity { private String authUserId; + private int timestamp; + private String redirectUri; private String action; private Set clientScopes = new HashSet<>(); @@ -68,6 +70,14 @@ public class MapAuthenticationSessionEntity { this.authUserId = authUserId; } + public int getTimestamp() { + return timestamp; + } + + public void setTimestamp(int timestamp) { + this.timestamp = timestamp; + } + public String getRedirectUri() { return redirectUri; } diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionAdapter.java index 670ff08a48..259018b781 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionAdapter.java @@ -82,11 +82,14 @@ public abstract class MapRootAuthenticationSessionAdapter extends AbstractRoo MapAuthenticationSessionEntity authSessionEntity = new MapAuthenticationSessionEntity(); authSessionEntity.setClientUUID(client.getId()); + int timestamp = Time.currentTime(); + authSessionEntity.setTimestamp(timestamp); + String tabId = generateTabId(); entity.getAuthenticationSessions().put(tabId, authSessionEntity); // Update our timestamp when adding new authenticationSession - entity.setTimestamp(Time.currentTime()); + entity.setTimestamp(timestamp); MapAuthenticationSessionAdapter authSession = new MapAuthenticationSessionAdapter(session, this, tabId, authSessionEntity); session.getContext().setAuthenticationSession(authSession); diff --git a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java index 030ef3e35a..1986f21ae3 100644 --- a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java +++ b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java @@ -204,5 +204,4 @@ public interface AuthenticationSessionModel extends CommonClientSessionModel { * @param clientScopes {@code Set} Can't be {@code null}. */ void setClientScopes(Set clientScopes); - } diff --git a/server-spi/src/main/java/org/keycloak/sessions/RootAuthenticationSessionModel.java b/server-spi/src/main/java/org/keycloak/sessions/RootAuthenticationSessionModel.java index b512f55ff2..f54d62c49c 100644 --- a/server-spi/src/main/java/org/keycloak/sessions/RootAuthenticationSessionModel.java +++ b/server-spi/src/main/java/org/keycloak/sessions/RootAuthenticationSessionModel.java @@ -78,7 +78,7 @@ public interface RootAuthenticationSessionModel { AuthenticationSessionModel getAuthenticationSession(ClientModel client, String tabId); /** - * Create a new authentication session and returns it. Overwrites existing session for particular client if already exists. + * Create a new authentication session and returns it. * @param client {@code ClientModel} Can't be {@code null}. * @return {@code AuthenticationSessionModel} non-null fresh authentication session. Never returns {@code null}. */ diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java index e33c9b13ea..a6836ec9ae 100644 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java @@ -31,7 +31,6 @@ import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.sessions.StickySessionEncoderProvider; import javax.ws.rs.core.UriInfo; -import java.util.AbstractMap.SimpleEntry; import java.util.List; import java.util.Objects; import java.util.Set; @@ -44,7 +43,7 @@ public class AuthenticationSessionManager { public static final String AUTH_SESSION_ID = "AUTH_SESSION_ID"; - public static final int AUTH_SESSION_LIMIT = 3; + public static final int AUTH_SESSION_COOKIE_LIMIT = 3; private static final Logger log = Logger.getLogger(AuthenticationSessionManager.class); @@ -189,7 +188,7 @@ public class AuthenticationSessionManager { AuthenticationManager.expireOldAuthSessionCookie(realm, session.getContext().getUri(), session.getContext().getConnection()); } - List authSessionIds = cookiesVal.stream().limit(AUTH_SESSION_LIMIT).collect(Collectors.toList()); + List authSessionIds = cookiesVal.stream().limit(AUTH_SESSION_COOKIE_LIMIT).collect(Collectors.toList()); if (authSessionIds.isEmpty()) { log.debugf("Not found AUTH_SESSION_ID cookie"); diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 49a046d9d0..8915103a51 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -62,12 +62,9 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.AuthenticationFlowResolver; import org.keycloak.models.utils.FormMessage; import org.keycloak.protocol.LoginProtocol; -import org.keycloak.protocol.LoginProtocolFactory; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.utils.RedirectUtils; -import org.keycloak.protocol.saml.SamlProtocol; -import org.keycloak.protocol.saml.SamlService; import org.keycloak.protocol.saml.SamlSessionUtils; import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.representations.AccessToken; diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index 53873cc758..49e5aded5c 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -72,7 +72,10 @@ }, "authenticationSessions": { - "provider": "${keycloak.authSession.provider:infinispan}" + "provider": "${keycloak.authSession.provider:infinispan}", + "infinispan": { + "authSessionsLimit": "${keycloak.authSessions.limit:300}" + } }, "userSessions": { diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java index 4ef001a6b1..0540d9053c 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java @@ -20,8 +20,10 @@ import org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory; import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory; import org.keycloak.connections.infinispan.InfinispanConnectionSpi; import org.keycloak.models.session.UserSessionPersisterSpi; +import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory; +import org.keycloak.sessions.AuthenticationSessionSpi; import org.keycloak.sessions.StickySessionEncoderProviderFactory; import org.keycloak.sessions.StickySessionEncoderSpi; import org.keycloak.testsuite.model.KeycloakModelParameters; @@ -47,6 +49,7 @@ public class Infinispan extends KeycloakModelParameters { private static final AtomicInteger NODE_COUNTER = new AtomicInteger(); static final Set> ALLOWED_SPIS = ImmutableSet.>builder() + .add(AuthenticationSessionSpi.class) .add(CacheRealmProviderSpi.class) .add(CacheUserProviderSpi.class) .add(InfinispanConnectionSpi.class) @@ -56,6 +59,7 @@ public class Infinispan extends KeycloakModelParameters { .build(); static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() + .add(InfinispanAuthenticationSessionProviderFactory.class) .add(InfinispanCacheRealmProviderFactory.class) .add(InfinispanClusterProviderFactory.class) .add(InfinispanConnectionProviderFactory.class) diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java index 9c67821f2d..d6ce5fd07f 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java @@ -25,6 +25,7 @@ import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderF import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory; import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory; import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; +import org.keycloak.sessions.AuthenticationSessionSpi; import org.keycloak.testsuite.model.KeycloakModelParameters; import org.keycloak.models.map.client.MapClientProviderFactory; import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; @@ -37,7 +38,6 @@ import org.keycloak.models.map.storage.MapStorageSpi; import org.keycloak.models.map.user.MapUserProviderFactory; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; -import org.keycloak.sessions.AuthenticationSessionSpi; import org.keycloak.testsuite.model.Config; import com.google.common.collect.ImmutableSet; import java.util.Set; @@ -49,6 +49,7 @@ import java.util.Set; public class Map extends KeycloakModelParameters { static final Set> ALLOWED_SPIS = ImmutableSet.>builder() + .add(AuthenticationSessionSpi.class) .add(MapStorageSpi.class) .build(); @@ -76,7 +77,7 @@ public class Map extends KeycloakModelParameters { @Override public void updateConfig(Config cf) { - cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapClientProviderFactory.PROVIDER_ID) + cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID) .spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID) .spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID) .spi("group").defaultProvider(MapGroupProviderFactory.PROVIDER_ID) diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/AuthenticationSessionTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/AuthenticationSessionTest.java new file mode 100644 index 0000000000..b983ab35cf --- /dev/null +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/AuthenticationSessionTest.java @@ -0,0 +1,91 @@ +/* + * 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.session; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.common.util.Time; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory; +import org.keycloak.sessions.AuthenticationSessionModel; +import org.keycloak.sessions.AuthenticationSessionProvider; +import org.keycloak.sessions.RootAuthenticationSessionModel; +import org.keycloak.testsuite.model.KeycloakModelTest; +import org.keycloak.testsuite.model.RequireProvider; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.keycloak.testsuite.model.session.UserSessionPersisterProviderTest.createClients; + +/** + * @author Martin Kanis + */ +@RequireProvider(value = AuthenticationSessionProvider.class, only = InfinispanAuthenticationSessionProviderFactory.PROVIDER_ID) +public class AuthenticationSessionTest extends KeycloakModelTest { + + private String realmId; + + @Override + public void createEnvironment(KeycloakSession s) { + RealmModel realm = s.realms().createRealm("test"); + realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); + + this.realmId = realm.getId(); + + createClients(s, realm); + } + + @Override + public void cleanEnvironment(KeycloakSession s) { + s.realms().removeRealm(realmId); + } + + @Test + public void testLimitAuthSessions() { + RootAuthenticationSessionModel ras = withRealm(realmId, (session, realm) -> session.authenticationSessions().createRootAuthenticationSession(realm)); + + List tabIds = withRealm(realmId, (session, realm) -> { + ClientModel client = realm.getClientByClientId("test-app"); + return IntStream.range(0, 300) + .mapToObj(i -> { + Time.setOffset(i); + return ras.createAuthenticationSession(client); + }) + .map(AuthenticationSessionModel::getTabId) + .collect(Collectors.toList()); + }); + + withRealm(realmId, (session, realm) -> { + ClientModel client = realm.getClientByClientId("test-app"); + + // create 301st auth session + AuthenticationSessionModel as = ras.createAuthenticationSession(client); + Assert.assertEquals(as, ras.getAuthenticationSession(client, as.getTabId())); + + // assert the first authentication session was deleted + Assert.assertNull(ras.getAuthenticationSession(client, tabIds.get(0))); + + return null; + }); + } +} diff --git a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json index 754c908bee..878fddcf2f 100755 --- a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json +++ b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json @@ -46,7 +46,10 @@ }, "authenticationSessions": { - "provider": "${keycloak.authSession.provider:infinispan}" + "provider": "${keycloak.authSession.provider:infinispan}", + "infinispan": { + "authSessionsLimit": "${keycloak.authSessions.limit:300}" + } }, "userSessions": {