diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 7e58a7f284..e955d3fdb5 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -527,7 +527,7 @@ public class LoginActionsService { if (tokenAuthSessionCompoundId != null) { // This can happen if the token contains ID but user opens the link in a new browser String sessionId = AuthenticationSessionCompoundId.encoded(tokenAuthSessionCompoundId).getRootSessionId(); - LoginActionsServiceChecks.checkNotLoggedInYet(tokenContext, sessionId); + LoginActionsServiceChecks.checkNotLoggedInYet(tokenContext, authSession, sessionId); } if (authSession == null) { diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java index f8b3e5005c..dfa224dc93 100644 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java @@ -111,13 +111,18 @@ public class LoginActionsServiceChecks { * Verifies that the authentication session has not yet been converted to user session, in other words * that the user has not yet completed authentication and logged in. */ - public static void checkNotLoggedInYet(ActionTokenContext context, String authSessionId) throws VerificationException { + public static void checkNotLoggedInYet(ActionTokenContext context, AuthenticationSessionModel authSessionFromCookie, String authSessionId) throws VerificationException { if (authSessionId == null) { return; } UserSessionModel userSession = context.getSession().sessions().getUserSession(context.getRealm(), authSessionId); - if (userSession != null && userSession.getUser().getRequiredActions().isEmpty()) { + boolean hasNoRequiredActions = + (userSession == null || userSession.getUser().getRequiredActions() == null || userSession.getUser().getRequiredActions().isEmpty()) + && + (authSessionFromCookie == null || authSessionFromCookie.getRequiredActions() == null || authSessionFromCookie.getRequiredActions().isEmpty()); + + if (userSession != null && hasNoRequiredActions) { LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class).setAuthenticationSession(context.getAuthenticationSession()) .setSuccess(Messages.ALREADY_LOGGED_IN); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java index 7fb0579d08..6be024dceb 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java @@ -17,7 +17,6 @@ package org.keycloak.testsuite.actions; import org.jboss.arquillian.drone.api.annotation.Drone; -import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionToken; import org.jboss.arquillian.graphene.page.Page; import org.junit.Assert; @@ -29,15 +28,17 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.models.Constants; +import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel.RequiredAction; -import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.admin.ApiUtil; -import org.keycloak.testsuite.broker.BrokerTestTools; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.cluster.AuthenticationSessionFailoverClusterTest; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.ProceedPage; @@ -46,11 +47,9 @@ import org.keycloak.testsuite.pages.InfoPage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.VerifyEmailPage; -import org.keycloak.testsuite.updaters.ClientAttributeUpdater; import org.keycloak.testsuite.updaters.UserAttributeUpdater; import org.keycloak.testsuite.util.GreenMailRule; import org.keycloak.testsuite.util.MailUtils; -import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.SecondBrowser; import org.keycloak.testsuite.util.UserActionTokenBuilder; import org.keycloak.testsuite.util.UserBuilder; @@ -65,6 +64,7 @@ import java.util.HashMap; import java.util.Map; import org.hamcrest.Matchers; +import org.junit.Assume; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -868,6 +868,48 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertThat(user.getRequiredActions(), Matchers.empty()); } + @Test + public void verifyEmailViaAuthSessionWhileLoggedIn() throws IOException, MessagingException { + Assume.assumeTrue("Works only on auth-server-undertow", + AuthServerTestEnricher.AUTH_SERVER_CONTAINER.equals(AuthServerTestEnricher.AUTH_SERVER_CONTAINER_DEFAULT)); + + UserAttributeUpdater userAttributeUpdater = new UserAttributeUpdater(testRealm().users().get(testUserId)); + userAttributeUpdater.setEmailVerified(false).update(); + + final String testRealmName = testRealm().toRepresentation().getRealm(); + accountPage.setAuthRealm(testRealmName); + oauth.realm(testRealmName).clientId("account").redirectUri(getAuthServerRoot() + "realms/" + testRealmName + "/account"); + loginPage.open(); + + String authSessionId = AuthenticationSessionFailoverClusterTest.getAuthSessionCookieValue(driver); + String realmId = testRealm().toRepresentation().getId(); + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealm(realmId); + RootAuthenticationSessionModel ras = session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId); + assertThat("Expecting single auth session", ras.getAuthenticationSessions().keySet(), Matchers.hasSize(1)); + ras.getAuthenticationSessions().forEach((id, as) -> as.addRequiredAction(RequiredAction.VERIFY_EMAIL)); + }); + loginPage.login("test-user@localhost", "password"); + + verifyEmailPage.assertCurrent(); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + MimeMessage message = greenMail.getLastReceivedMessage(); + + String verificationUrl = getPasswordResetEmailLink(message); + + // confirm + driver.navigate().to(verificationUrl); + + // back to account, already logged in + accountPage.assertCurrent(); + + // email should be verified and required actions empty + UserRepresentation user = testRealm().users().get(testUserId).toRepresentation(); + Assert.assertTrue(user.isEmailVerified()); + Assert.assertThat(user.getRequiredActions(), Matchers.empty()); + } + @Test public void verifyEmailInNewBrowserWhileLoggedInFirstBrowser() throws IOException, MessagingException { UserAttributeUpdater userAttributeUpdater = new UserAttributeUpdater(testRealm().users().get(testUserId)); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AuthenticationSessionFailoverClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AuthenticationSessionFailoverClusterTest.java index 817bdc6b25..a20e65b056 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AuthenticationSessionFailoverClusterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AuthenticationSessionFailoverClusterTest.java @@ -161,7 +161,7 @@ public class AuthenticationSessionFailoverClusterTest extends AbstractFailoverCl appPage.assertCurrent(); } - static String getAuthSessionCookieValue(WebDriver driver) { + public static String getAuthSessionCookieValue(WebDriver driver) { Cookie authSessionCookie = driver.manage().getCookieNamed(AuthenticationSessionManager.AUTH_SESSION_ID); Assert.assertNotNull(authSessionCookie); return authSessionCookie.getValue();