8000 Use note to detect the IDP verify email action is already done by rmartinc · Pull Request #33304 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
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;
Expand Down Expand Up @@ -81,17 +80,21 @@ public Response handleToken(IdpVerifyAccountLinkActionToken token, ActionTokenCo

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();
if (authSession.getAuthNote(IdpEmailVerificationAuthenticator.VERIFY_ACCOUNT_IDP_USERNAME) != null) {
return sendEmailAlreadyVerified(session, event, user);
}

event.success();
AuthenticationSessionManager asm = new AuthenticationSessionManager(session);

if (tokenContext.isAuthenticationSessionFresh()) {
AuthenticationSessionCompoundId compoundId = AuthenticationSessionCompoundId.encoded(token.getCompoundAuthenticationSessionId());
ClientModel originalClient = realm.getClientById(compoundId.getClientUUID());
AuthenticationSessionModel origAuthSession = asm.getAuthenticationSessionByIdAndClient(realm,
compoundId.getRootSessionId(), originalClient, compoundId.getTabId());
if (origAuthSession == null || origAuthSession.getAuthNote(IdpEmailVerificationAuthenticator.VERIFY_ACCOUNT_IDP_USERNAME) != null) {
return sendEmailAlreadyVerified(session, event, user);
}

token.setOriginalCompoundAuthenticationSessionId(token.getCompoundAuthenticationSessionId());

String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authSession).getEncodedId();
Expand All @@ -109,9 +112,9 @@ public Response handleToken(IdpVerifyAccountLinkActionToken token, ActionTokenCo

// verify user email as we know it is valid as this entry point would never have gotten here.
user.setEmailVerified(true);
event.success();

if (token.getOriginalCompoundAuthenticationSessionId() != null) {
AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
asm.removeAuthenticationSession(realm, authSession, true);

AuthenticationSessionCompoundId compoundId = AuthenticationSessionCompoundId.encoded(token.getOriginalCompoundAuthenticationSessionId());
Expand Down Expand Up @@ -140,8 +143,11 @@ public Response handleToken(IdpVerifyAccountLinkActionToken token, ActionTokenCo
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);
private Response sendEmailAlreadyVerified(KeycloakSession session, EventBuilder event, UserModel user) {
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ public class IdpLinkEmailPage extends AbstractPage {
@FindBy(id = "instruction1")
private WebElement message;

@FindBy(linkText = "Click here")
@FindBy(xpath = "//p[@id='instruction2']/a[text() = 'Click here']")
private WebElement resendEmailLink;

@FindBy(xpath = "//p[@id='instruction3']/a[text() = 'Click here']")
private WebElement continueLink;

@Override
public boolean isCurrent() {
return PageUtils.getPageTitle(driver).startsWith("Link ");
Expand All @@ -48,4 +51,8 @@ public String getMessage() {
public void resendEmail() {
resendEmailLink.click();
}

public void continueLink() {
continueLink.click();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertTrue;
import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
Expand Down Expand Up @@ -1018,6 +1019,53 @@ public void testLinkAccountByEmailVerificationTwice() {
waitForPage(driver, "your email address has been verified already.", false);
}

@Test
public void testLinkAccountByEmailVerificationInAnotherBrowser() {
RealmResource realm = adminClient.realm(bc.consumerRealmName());

UserResource userResource = realm.users().get(createUser("consumer"));
UserRepresentation consumerUser = userResource.toRepresentation();

consumerUser.setEmail(bc.getUserEmail());
consumerUser.setEmailVerified(true);
userResource.update(consumerUser);
configureSMTPServer();

oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());

logInWithBroker(bc);

//link account by email
waitForPage(driver, "update account information", false);
Assert.assertTrue(updateAccountInformationPage.isCurrent());
updateAccountInformationPage.updateAccountInformation("FirstName", "LastName");
waitForPage(driver 8488 , "account already exists", false);
assertTrue(idpConfirmLinkPage.isCurrent());
assertEquals("User with email user@localhost.com already exists. How do you want to continue?", idpConfirmLinkPage.getMessage());
idpConfirmLinkPage.clickLinkAccount();
idpLinkEmailPage.assertCurrent();

String url = assertEmailAndGetUrl(MailServerConfiguration.FROM, USER_EMAIL,
"Someone wants to link your ", false);

// in the second browser confirm the mail
driver2.navigate().to(url);
assertThat(driver2.findElement(By.className("instruction")).getText(), startsWith("Confirm linking the account"));
driver2.findElement(By.linkText("» Click here to proceed")).click();
assertThat(driver2.findElement(By.className("instruction")).getText(), startsWith("You successfully verified your email."));

idpLinkEmailPage.continueLink();

//test if user is logged in
assertTrue(driver.getCurrentUrl().startsWith(getConsumerRoot() + "/auth/realms/master/app/"));
// check user is linked
List<FederatedIdentityRepresentation> identities = userResource.getFederatedIdentity();
assertEquals(1, identities.size());
assertEquals(bc.getIDPAlias(), identities.iterator().next().getIdentityProvider());
assertEquals(bc.getUserLogin(), identities.iterator().next().getUserName());
}

@Test
public void testLinkAccountByEmailVerificationToEmailVerifiedUser() {
// set up a user with verified email
Expand Down
0