diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 4da92ecacc..81cca62871 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -132,13 +132,13 @@ public class AuthenticationManager { int currentTime = Time.currentTime(); // Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed - int maxIdle = (userSession.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0 ? - realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout()) + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS; + int maxIdle = userSession.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0 ? + realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout(); int maxLifespan = userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan(); - boolean sessionMaxOk = userSession.getStarted() + maxLifespan > currentTime; - boolean sessionIdleOk = userSession.getLastSessionRefresh() + maxIdle > currentTime; + boolean sessionIdleOk = maxIdle > currentTime - userSession.getLastSessionRefresh() - SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS; + boolean sessionMaxOk = maxLifespan > currentTime - userSession.getStarted(); return sessionIdleOk && sessionMaxOk; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/session/SessionTimeoutValidationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/session/SessionTimeoutValidationTest.java new file mode 100644 index 0000000000..45e68df092 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/session/SessionTimeoutValidationTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2019 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.session; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.TargetsContainer; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.keycloak.admin.client.resource.UserResource; +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.representations.idm.RealmRepresentation; +import org.keycloak.services.managers.AuthenticationManager; + +import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.arquillian.annotation.ModelTest; +import org.keycloak.testsuite.runonserver.RunOnServerDeployment; + +import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT; + +public class SessionTimeoutValidationTest extends AbstractTestRealmKeycloakTest { + + @Deployment + @TargetsContainer(AUTH_SERVER_CURRENT) + public static WebArchive deploy() { + return RunOnServerDeployment.create(UserResource.class, SessionTimeoutValidationTest.class) + .addPackages(true, + "org.keycloak.testsuite", + "org.keycloak.testsuite.model"); + } + + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + + } + + + @Before + public void before() { + testingClient.server().run( session -> { + RealmModel realm = session.realms().getRealmByName("test"); + realm = session.realms().getRealm("test"); + session.users().addUser(realm, "user1"); + }); + } + + + @After + public void after() { + testingClient.server().run( session -> { + RealmModel realm = session.realms().getRealmByName("test"); + session.sessions().removeUserSessions(realm); + UserModel user1 = session.users().getUserByUsername("user1", realm); + + UserManager um = new UserManager(session); + if (user1 != null) { + um.removeUser(realm, user1); + } + }); + } + + + @Test + @ModelTest + public void testIsSessionValid(KeycloakSession session) { + + // KEYCLOAK-9833 Large SSO Session Idle/SSO Session Max causes login failure + RealmModel realm = session.realms().getRealmByName("test"); + int ssoSessionIdleTimeoutOrig = realm.getSsoSessionIdleTimeout(); + int ssoSessionMaxLifespanOrig = realm.getSsoSessionMaxLifespan(); + UserSessionModel userSessionModel = + session.sessions().createUserSession( + realm, + session.users().getUserByUsername("user1", realm), + "user1", "127.0.0.1", "form", true, null, null + ); + + realm.setSsoSessionIdleTimeout(Integer.MAX_VALUE); + Assert.assertTrue("Session validataion with large SsoSessionIdleTimeout failed", + AuthenticationManager.isSessionValid(realm, userSessionModel)); + + realm.setSsoSessionMaxLifespan(Integer.MAX_VALUE); + Assert.assertTrue("Session validataion with large SsoSessionMaxLifespan failed", + AuthenticationManager.isSessionValid(realm, userSessionModel)); + + realm.setSsoSessionIdleTimeout(ssoSessionIdleTimeoutOrig); + realm.setSsoSessionMaxLifespan(ssoSessionMaxLifespanOrig); + } +}