diff --git a/docs/documentation/release_notes/topics/24_0_0.adoc b/docs/documentation/release_notes/topics/24_0_0.adoc index 3541defe86..0218864d68 100644 --- a/docs/documentation/release_notes/topics/24_0_0.adoc +++ b/docs/documentation/release_notes/topics/24_0_0.adoc @@ -94,4 +94,18 @@ For more details, see link:{upgradingguide_link}[{upgradingguide_name}]. In this release, the server will render the update profile page when the user is authenticating through a broker for the first time using the `idp-review-user-profile.ftl` template. -For more details, see link:{upgradingguide_link}[{upgradingguide_name}]. \ No newline at end of file +For more details, see link:{upgradingguide_link}[{upgradingguide_name}]. + += Performing actions on behalf of another user is not longer possible when the user is already authenticated + +In this release, you can no longer perform actions such as email verification if the user is already authenticated +and the action is bound to another user. For instance, a user can not complete the verification email flow if the email link +is bound to a different account. + += Changes to the email verification flow + +In this release, if a user tries to follow the link to verify the email and the email was previously verified, a proper message +will be shown. + +In addition to that, a new error (`EMAIL_ALREADY_VERIFIED`) event will be fired to indicate an attempt to verify an already verified email. You can +use this event to track possible attempts to hijack user accounts in case the link has leaked or to alert users if they do not recognize the action. \ No newline at end of file diff --git a/server-spi-private/src/main/java/org/keycloak/events/Errors.java b/server-spi-private/src/main/java/org/keycloak/events/Errors.java index 797a2e05c0..a32e6a1281 100755 --- a/server-spi-private/src/main/java/org/keycloak/events/Errors.java +++ b/server-spi-private/src/main/java/org/keycloak/events/Errors.java @@ -45,6 +45,7 @@ public interface Errors { String USERNAME_MISSING = "username_missing"; String USERNAME_IN_USE = "username_in_use"; String EMAIL_IN_USE = "email_in_use"; + String EMAIL_ALREADY_VERIFIED = "email_already_verified"; String INVALID_REDIRECT_URI = "invalid_redirect_uri"; String INVALID_CODE = "invalid_code"; diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java index 030cabc444..7ab2a0e558 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java @@ -28,13 +28,17 @@ import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.services.Urls; +import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationSessionManager; import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionCompoundId; import org.keycloak.sessions.AuthenticationSessionModel; import java.util.Collections; +import java.util.stream.Stream; + import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; @@ -73,10 +77,20 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH event.event(EventType.IDENTITY_PROVIDER_LINK_ACCOUNT) .detail(Details.EMAIL, user.getEmail()) .detail(Details.IDENTITY_PROVIDER, token.getIdentityProviderAlias()) - .detail(Details.IDENTITY_PROVIDER_USERNAME, token.getIdentityProviderUsername()) - .success(); + .detail(Details.IDENTITY_PROVIDER_USERNAME, token.getIdentityProviderUsername()); AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession(); + + if (user.isEmailVerified() && !isVerifyEmailActionSet(user, authSession)) { + event.user(user).error(Errors.EMAIL_ALREADY_VERIFIED); + return session.getProvider(LoginFormsProvider.class) + .setAuthenticationSession(session.getContext().getAuthenticationSession()) + .setInfo(Messages.EMAIL_VERIFIED_ALREADY, user.getEmail()) + .createInfoPage(); + } + + event.success(); + if (tokenContext.isAuthenticationSessionFresh()) { token.setOriginalCompoundAuthenticationSessionId(token.getCompoundAuthenticationSessionId()); @@ -126,4 +140,8 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH return tokenContext.brokerFlow(null, null, authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH)); } + private boolean isVerifyEmailActionSet(UserModel user, AuthenticationSessionModel authSession) { + return Stream.concat(user.getRequiredActionsStream(), authSession.getRequiredActions().stream()) + .anyMatch(RequiredAction.VERIFY_EMAIL.name()::equals); + } } diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java index ceb6036091..6db85aba78 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java @@ -33,6 +33,8 @@ import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionCompoundId; import org.keycloak.sessions.AuthenticationSessionModel; import java.util.Objects; +import java.util.stream.Stream; + import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; @@ -66,14 +68,22 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHandler tokenContext) { UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser(); + KeycloakSession session = tokenContext.getSession(); + AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession(); EventBuilder event = tokenContext.getEvent(); event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()); - AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession(); + if (user.isEmailVerified() && !isVerifyEmailActionSet(user, authSession)) { + event.user(user).error(Errors.EMAIL_ALREADY_VERIFIED); + return session.getProvider(LoginFormsProvider.class) + .setAuthenticationSession(authSession) + .setInfo(Messages.EMAIL_VERIFIED_ALREADY, user.getEmail()) + .createInfoPage(); + } + final UriInfo uriInfo = tokenContext.getUriInfo(); final RealmModel realm = tokenContext.getRealm(); - final KeycloakSession session = tokenContext.getSession(); if (tokenContext.isAuthenticationSessionFresh()) { // Update the authentication session in the token @@ -100,10 +110,10 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHandler void checkNotLoggedInYet(ActionTokenContext context, AuthenticationSessionModel authSessionFromCookie, String authSessionId) throws VerificationException { - if (authSessionId == null) { - return; - } - - UserSessionModel userSession = context.getSession().sessions().getUserSession(context.getRealm(), authSessionId); - boolean hasNoRequiredActions = - (userSession == null || userSession.getUser().getRequiredActionsStream().count() == 0) - && - (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); - - if (context.getSession().getContext().getClient() == null) { - loginForm.setAttribute(Constants.SKIP_LINK, true); - } - - throw new LoginActionsServiceException(loginForm.createInfoPage()); - } - } - /** * Verifies whether the user given by ID both exists in the current realm. If yes, * it optionally also injects the user using the given function (e.g. into session context). */ - public static void checkIsUserValid(KeycloakSession session, RealmModel realm, String userId, Consumer userSetter) throws VerificationException { + public static void checkIsUserValid(KeycloakSession session, RealmModel realm, String userId, Consumer userSetter, EventBuilder event) throws VerificationException { UserModel user = userId == null ? null : session.users().getUserById(realm, userId); if (user == null) { @@ -154,6 +130,21 @@ public class LoginActionsServiceChecks { throw new ExplainedVerificationException(Errors.USER_DISABLED, Messages.ACCOUNT_DISABLED); } + AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, true); + + if (authResult != null) { + UserSessionModel userSession = authResult.getSession(); + if (!user.equals(userSession.getUser())) { + // do not allow authenticated users performing actions that are bound to other user and fire an event + // it might be an attempt to hijack a user account or perform actions on behalf of others + // we don't support yet multiple accounts within a same browser session + event.detail(Details.EXISTING_USER, userSession.getUser().getId()); + event.error(Errors.DIFFERENT_USER_AUTHENTICATED); + AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession(); + throw new ErrorPageException(session, authSession, Response.Status.BAD_REQUEST, Messages.DIFFERENT_USER_AUTHENTICATED, userSession.getUser().getUsername()); + } + } + if (userSetter != null) { userSetter.accept(user); } @@ -163,9 +154,9 @@ public class LoginActionsServiceChecks { * Verifies whether the user given by ID both exists in the current realm. If yes, * it optionally also injects the user using the given function (e.g. into session context). */ - public static void checkIsUserValid(T token, ActionTokenContext context) throws VerificationException { + public static void checkIsUserValid(T token, ActionTokenContext context, EventBuilder event) throws VerificationException { try { - checkIsUserValid(context.getSession(), context.getRealm(), token.getUserId(), context.getAuthenticationSession()::setAuthenticatedUser); + checkIsUserValid(context.getSession(), context.getRealm(), token.getUserId(), context.getAuthenticationSession()::setAuthenticatedUser, event); } catch (ExplainedVerificationException ex) { throw new ExplainedTokenVerificationException(token, ex); } 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 f278462705..7ba1a461f7 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 @@ -32,6 +32,7 @@ import org.keycloak.events.EventType; import org.keycloak.models.Constants; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel.RequiredAction; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -81,6 +82,8 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * @author Stian Thorgersen @@ -171,7 +174,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getReceivedMessages()[0]; - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost"); EventRepresentation sendEvent = emailEvent.assertEvent(); @@ -209,7 +212,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo EventRepresentation sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).user(userId).detail(Details.USERNAME, "verifyemail").detail("email", "email@mail.com").assertEvent(); String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver.navigate().to(verificationUrl.trim()); @@ -225,6 +228,60 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo events.expectLogin().user(userId).session(mailCodeId).detail(Details.USERNAME, "verifyemail").assertEvent(); } + @Test + public void verifyEmailFromAnotherAccountWhenUserIsAuthenticated() throws Exception { + loginPage.open(); + loginPage.clickRegister(); + String username1 = KeycloakModelUtils.generateId(); + registerPage.register("firstName", "lastName", username1 + "@mail.com", username1, "password", "password"); + verifyEmailPage.assertCurrent(); + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + MimeMessage message = greenMail.getReceivedMessages()[0]; + String verificationLink1 = getEmailLink(message); + + loginPage.open(); + loginPage.clickRegister(); + String username2 = KeycloakModelUtils.generateId(); + registerPage.register("firstName", "lastName", username2 + "@mail.com", username2, "password", "password"); + verifyEmailPage.assertCurrent(); + Assert.assertEquals(2, greenMail.getReceivedMessages().length); + message = greenMail.getReceivedMessages()[1]; + String verificationLink2 = getEmailLink(message); + driver.navigate().to(verificationLink2.trim()); + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + driver.navigate().to(verificationLink1.trim()); + assertTrue(errorPage.getError().contains("You are already authenticated as different user")); + UserRepresentation user1 = testRealm().users().search(username1).get(0); + UserRepresentation user2 = testRealm().users().search(username2).get(0); + assertFalse(user1.isEmailVerified()); + assertTrue(user2.isEmailVerified()); + } + + @Test + public void verifyEmailFromAnotherAccountAfterEmalIsVerified() throws Exception { + loginPage.open(); + loginPage.clickRegister(); + String username1 = KeycloakModelUtils.generateId(); + registerPage.register("firstName", "lastName", username1 + "@mail.com", username1, "password", "password"); + verifyEmailPage.assertCurrent(); + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + MimeMessage message = greenMail.getReceivedMessages()[0]; + String verificationLink1 = getEmailLink(message); + + loginPage.open(); + loginPage.clickRegister(); + String username2 = KeycloakModelUtils.generateId(); + registerPage.register("firstName", "lastName", username2 + "@mail.com", username2, "password", "password"); + verifyEmailPage.assertCurrent(); + Assert.assertEquals(2, greenMail.getReceivedMessages().length); + message = greenMail.getReceivedMessages()[1]; + String verificationLink2 = getEmailLink(message); + + driver.navigate().to(verificationLink1.trim()); + driver.navigate().to(verificationLink2.trim()); + assertTrue(errorPage.getError().contains("You are already authenticated as different user")); + } + @Test public void verifyEmailResend() throws IOException, MessagingException { loginPage.open(); @@ -250,7 +307,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(2, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver.navigate().to(verificationUrl.trim()); @@ -294,7 +351,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(2, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver.navigate().to(verificationUrl.trim()); @@ -324,7 +381,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message1 = greenMail.getReceivedMessages()[0]; - String verificationUrl1 = getPasswordResetEmailLink(message1); + String verificationUrl1 = getEmailLink(message1); driver.navigate().to(verificationUrl1.trim()); @@ -333,12 +390,16 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message2 = greenMail.getReceivedMessages()[1]; - String verificationUrl2 = getPasswordResetEmailLink(message2); + String verificationUrl2 = getEmailLink(message2); + events.clear(); driver.navigate().to(verificationUrl2.trim()); - + events.expectRequiredAction(EventType.VERIFY_EMAIL) + .error(Errors.EMAIL_ALREADY_VERIFIED) + .detail(Details.REDIRECT_URI, Matchers.any(String.class)) + .assertEvent(); infoPage.assertCurrent(); - Assert.assertEquals("You are already logged in.", infoPage.getInfo()); + Assert.assertEquals("Your email address has been verified already.", infoPage.getInfo()); } @Test @@ -354,7 +415,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message1 = greenMail.getReceivedMessages()[0]; - String verificationUrl1 = getPasswordResetEmailLink(message1); + String verificationUrl1 = getEmailLink(message1); driver.navigate().to(verificationUrl1.trim()); @@ -362,14 +423,31 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message2 = greenMail.getReceivedMessages()[1]; - String verificationUrl2 = getPasswordResetEmailLink(message2); + String verificationUrl2 = getEmailLink(message2); driver.navigate().to(verificationUrl2.trim()); - proceedPage.assertCurrent(); - proceedPage.clickProceedLink(); - infoPage.assertCurrent(); - assertEquals("Your email address has been verified.", infoPage.getInfo()); + assertEquals("Your email address has been verified already.", infoPage.getInfo()); + } + + @Test + public void verifyEmailResendAndVerifyWithLatestLink() throws IOException, MessagingException { + // Email verification can be performed any number of times + loginPage.open(); + loginPage.login("test-user@localhost", "password"); + verifyEmailPage.clickResendEmail(); + verifyEmailPage.assertCurrent(); + Assert.assertEquals(2, greenMail.getReceivedMessages().length); + MimeMessage message1 = greenMail.getReceivedMessages()[0]; + String verificationUrl1 = getEmailLink(message1); + + MimeMessage message2 = greenMail.getReceivedMessages()[1]; + String verificationUrl2 = getEmailLink(message2); + driver.navigate().to(verificationUrl2.trim()); + appPage.assertCurrent(); + + driver.navigate().to(verificationUrl1.trim()); + assertEquals("Your email address has been verified already.", infoPage.getInfo()); } @Test @@ -383,7 +461,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost"); EventRepresentation sendEvent = emailEvent.assertEvent(); @@ -422,7 +500,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); verificationUrl = KeycloakUriBuilder.fromUri(verificationUrl).replaceQueryParam(Constants.KEY, "foo").build().toString(); @@ -453,7 +531,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); events.poll(); @@ -495,7 +573,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); events.poll(); @@ -540,7 +618,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); events.poll(); @@ -578,7 +656,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); events.poll(); @@ -606,7 +684,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo } - public static String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException { + public static String getEmailLink(MimeMessage message) throws IOException, MessagingException { return MailUtils.getPasswordResetEmailLink(message); } @@ -621,7 +699,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver.manage().deleteAllCookies(); @@ -650,7 +728,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); // open link in the second browser without the session driver2.navigate().to(verificationUrl.trim()); @@ -688,7 +766,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver.navigate().to(verificationUrl.trim()); @@ -707,7 +785,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver.manage().deleteAllCookies(); @@ -732,7 +810,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver.manage().deleteAllCookies(); @@ -808,7 +886,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver2.navigate().to(verificationUrl.trim()); @@ -846,7 +924,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); // confirm driver.navigate().to(verificationUrl); @@ -856,7 +934,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo // email should be verified and required actions empty UserRepresentation user = testRealm().users().get(testUserId).toRepresentation(); - Assert.assertTrue(user.isEmailVerified()); + assertTrue(user.isEmailVerified()); assertThat(user.getRequiredActions(), Matchers.empty()); } @@ -885,7 +963,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); // confirm driver.navigate().to(verificationUrl); @@ -895,7 +973,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo // email should be verified and required actions empty UserRepresentation user = testRealm().users().get(testUserId).toRepresentation(); - Assert.assertTrue(user.isEmailVerified()); + assertTrue(user.isEmailVerified()); assertThat(user.getRequiredActions(), Matchers.empty()); } @@ -917,7 +995,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo Assert.assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); // confirm in the second browser driver2.navigate().to(verificationUrl); @@ -939,7 +1017,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo // email should be verified and required actions empty UserRepresentation user = testRealm().users().get(testUserId).toRepresentation(); - Assert.assertTrue(user.isEmailVerified()); + assertTrue(user.isEmailVerified()); assertThat(user.getRequiredActions(), Matchers.empty()); // after refresh in the first browser the app should be shown @@ -964,7 +1042,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo MimeMessage message = greenMail.getLastReceivedMessage(); - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); try { setTimeOffset(360); @@ -989,7 +1067,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo assertEquals(1, greenMail.getReceivedMessages().length); MimeMessage message = greenMail.getReceivedMessages()[0]; - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); UserResource user = testRealm().users().get(testUserId); UserRepresentation userRep = user.toRepresentation(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java index 490e097513..ef4c635cd4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java @@ -909,13 +909,14 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(consumerUser.getId()).toRepresentation().isEmailVerified()); driver.navigate().to(url); - waitForPage(driver, "you are already logged in.", false); + waitForPage(driver, "your email address has been verified already.", false); AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), "consumer"); driver.navigate().to(url); - waitForPage(driver, "confirm linking the account testuser of identity provider " + bc.getIDPAlias() + " with your account.", false); - proceedPage.clickProceedLink(); - waitForPage(driver, "you successfully verified your email. please go back to your original browser and continue there with the login.", false); + waitForPage(driver, "your email address has been verified already.", false); + + driver2.navigate().to(url); + waitForPage(driver, "your email address has been verified already.", false); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java index 87bb3752b5..60e87111da 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java @@ -92,7 +92,7 @@ import static org.keycloak.storage.UserStorageProviderModel.EVICTION_HOUR; import static org.keycloak.storage.UserStorageProviderModel.EVICTION_MINUTE; import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED; import static org.keycloak.storage.UserStorageProviderModel.MAX_LIFESPAN; -import static org.keycloak.testsuite.actions.RequiredActionEmailVerificationTest.getPasswordResetEmailLink; +import static org.keycloak.testsuite.actions.RequiredActionEmailVerificationTest.getEmailLink; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith; @@ -393,7 +393,7 @@ public class UserStorageTest extends AbstractAuthTest { MimeMessage message = greenMail.getReceivedMessages()[0]; - String verificationUrl = getPasswordResetEmailLink(message); + String verificationUrl = getEmailLink(message); driver.navigate().to(verificationUrl.trim()); diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties index 91262ea18a..78f07d3ec1 100755 --- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -353,6 +353,7 @@ realmSupportsNoCredentialsMessage=Realm does not support any credential type. credentialSetupRequired=Cannot login, credential setup required. identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with. emailVerifiedMessage=Your email address has been verified. +emailVerifiedAlreadyMessage=Your email address has been verified already. staleEmailVerificationLink=The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email. identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user. confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account.