diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java index 4d6b551bc4..004f88b4e0 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java @@ -43,10 +43,11 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken { public IdpVerifyAccountLinkActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, - String identityProviderUsername, String identityProviderAlias) { + String identityProviderUsername, String identityProviderAlias, String clientId) { super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId); this.identityProviderUsername = identityProviderUsername; this.identityProviderAlias = identityProviderAlias; + this.issuedFor = clientId; } private IdpVerifyAccountLinkActionToken() { diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionToken.java b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionToken.java index 5776617722..bb7969c07a 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionToken.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionToken.java @@ -27,8 +27,9 @@ public class ResetCredentialsActionToken extends DefaultActionToken { public static final String TOKEN_TYPE = "reset-credentials"; - public ResetCredentialsActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId) { + public ResetCredentialsActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String clientId) { super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId); + this.issuedFor = clientId; } private ResetCredentialsActionToken() { diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionToken.java b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionToken.java index 2ccd571623..4d0d5a5041 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionToken.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionToken.java @@ -37,9 +37,10 @@ public class VerifyEmailActionToken extends DefaultActionToken { @JsonProperty(value = JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID) private String originalAuthenticationSessionId; - public VerifyEmailActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String email) { + public VerifyEmailActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String email, String clientId) { super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId); this.email = email; + this.issuedFor = clientId; } private VerifyEmailActionToken() { diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java index 3634d3cb38..fa8bdea102 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java @@ -131,7 +131,7 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authSession).getEncodedId(); IdpVerifyAccountLinkActionToken token = new IdpVerifyAccountLinkActionToken( existingUser.getId(), absoluteExpirationInSecs, authSessionEncodedId, - brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias() + brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias(), authSession.getClient().getClientId() ); UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo), authSession.getClient().getClientId(), authSession.getTabId()); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java index e4befe2ae6..4fb8826c6f 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java @@ -91,7 +91,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory // We send the secret in the email in a link as a query param. String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authenticationSession).getEncodedId(); - ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId); + ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId, authenticationSession.getClient().getClientId()); String link = UriBuilder .fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo()))) .build() diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java index c29c616743..876dcd4cd9 100755 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java @@ -143,7 +143,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor int absoluteExpirationInSecs = Time.currentTime() + validityInSecs; String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authSession).getEncodedId(); - VerifyEmailActionToken token = new VerifyEmailActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId, user.getEmail()); + VerifyEmailActionToken token = new VerifyEmailActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId, user.getEmail(), authSession.getClient().getClientId()); UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo), authSession.getClient().getClientId(), authSession.getTabId()); String link = builder.build(realm.getName()).toString(); 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 ca944379a7..83c294cb52 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,6 +17,7 @@ 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,6 +30,7 @@ import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.models.Constants; 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; @@ -43,9 +45,11 @@ 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; @@ -384,9 +388,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo events.expectRequiredAction(EventType.VERIFY_EMAIL) .user(testUserId) .detail(Details.CODE_ID, Matchers.not(Matchers.is(mailCodeId))) - .client(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID) // as authentication sessions are browser-specific, - // the client and redirect_uri is unrelated to - // the "test-app" specified in loginPage.open() + .client(oauth.getClientId()) // the "test-app" client specified in loginPage.open() is expected .detail(Details.REDIRECT_URI, Matchers.any(String.class)) .assertEvent(); @@ -629,6 +631,39 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo } } + @Test + public void verifyEmailNewBrowserSessionPreserveClient() throws IOException, MessagingException { + loginPage.open(); + loginPage.login("test-user@localhost", "password"); + + verifyEmailPage.assertCurrent(); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getLastReceivedMessage(); + + String verificationUrl = getPasswordResetEmailLink(message); + + // open link in the second browser without the session + driver2.navigate().to(verificationUrl.trim()); + + // follow the link + final WebElement proceedLink = driver2.findElement(By.linkText("» Click here to proceed")); + assertThat(proceedLink, Matchers.notNullValue()); + + // check if the initial client is preserved + String link = proceedLink.getAttribute("href"); + assertThat(link, Matchers.containsString("client_id=test-app")); + proceedLink.click(); + + // confirmation in the second browser + assertThat(driver2.getPageSource(), Matchers.containsString("kc-info-message")); + assertThat(driver2.getPageSource(), Matchers.containsString("Your email address has been verified.")); + + final WebElement backToApplicationLink = driver2.findElement(By.linkText("« Back to Application")); + assertThat(backToApplicationLink, Matchers.notNullValue()); + } + @Test public void verifyEmailDuringAuthFlow() throws IOException, MessagingException { try (Closeable u = new UserAttributeUpdater(testRealm().users().get(testUserId)) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java index 09f289f46e..f80a4360f7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java @@ -19,9 +19,11 @@ package org.keycloak.testsuite.broker; import java.util.List; +import org.hamcrest.Matchers; import org.jboss.arquillian.graphene.page.Page; import org.junit.After; import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; @@ -36,6 +38,7 @@ import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.UpdateAccountInformationPage; import org.openqa.selenium.TimeoutException; +import static org.junit.Assert.assertThat; import static org.keycloak.testsuite.broker.BrokerTestTools.encodeUrl; import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage; @@ -142,6 +145,22 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest { return BrokerTestTools.getAuthRoot(suiteContext) + "/auth/realms/" + realmName + "/account/password"; } + /** + * Get the login page for an existing client in provided realm + * @param realmName Name of the realm + * @param clientId ClientId of a client. Client has to exists in the realm. + * @return Login URL + */ + protected String getLoginUrl(String realmName, String clientId) { + List clients = adminClient.realm(realmName).clients().findByClientId(clientId); + + assertThat(clients, Matchers.is(Matchers.not(Matchers.empty()))); + + String redirectURI = clients.get(0).getBaseUrl(); + + return BrokerTestTools.getAuthRoot(suiteContext) + "/auth/realms/" + realmName + "/protocol/openid-connect/auth?client_id=" + + clientId + "&redirect_uri=" + redirectURI + "&response_type=code&scope=openid"; + } protected void logoutFromRealm(String realm) { driver.navigate().to(BrokerTestTools.getAuthRoot(suiteContext) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java index f9f141d265..8aaaf0be53 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java @@ -1,5 +1,8 @@ package org.keycloak.testsuite.broker; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.jboss.arquillian.drone.api.annotation.Drone; import org.junit.Before; import org.junit.Test; @@ -16,6 +19,7 @@ import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.pages.ConsentPage; import org.keycloak.testsuite.util.*; +import org.openqa.selenium.By; import org.openqa.selenium.TimeoutException; import java.util.Collections; @@ -27,11 +31,14 @@ import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient; +import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername; import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword; import static org.keycloak.testsuite.broker.BrokerTestConstants.USER_EMAIL; import static org.keycloak.testsuite.util.MailAssert.assertEmailAndGetUrl; import org.jboss.arquillian.graphene.page.Page; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; import javax.ws.rs.core.Response; @@ -49,6 +56,10 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest { protected IdentityProviderResource identityProviderResource; + @Drone + @SecondBrowser + protected WebDriver driver2; + @Before public void beforeBrokerTest() { log.debug("creating user for realm " + bc.providerRealmName()); @@ -181,15 +192,10 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest { MailServer.createEmailAccount(USER_EMAIL, "password"); try { - //configure smpt server in the realm - RealmRepresentation master = adminClient.realm(bc.consumerRealmName()).toRepresentation(); - master.setSmtpServer(suiteContext.getSmtpServer()); - adminClient.realm(bc.consumerRealmName()).update(master); - + configureSMPTServer(); + //create user on consumer's site who should be linked later - UserRepresentation newUser = UserBuilder.create().username("consumer").email(USER_EMAIL).enabled(true).build(); - String userId = createUserWithAdminClient(adminClient.realm(bc.consumerRealmName()), newUser); - resetUserPassword(adminClient.realm(bc.consumerRealmName()).users().get(userId), "password", false); + String linkedUserId = createUser("consumer"); //test driver.navigate().to(getAccountUrl(bc.consumerRealmName())); @@ -228,8 +234,74 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest { assertEquals(accountPage.buildUri().toASCIIString().replace("master", "consumer") + "/", driver.getCurrentUrl()); //test if the user has verified email - assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(userId).toRepresentation().isEmailVerified()); + assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(linkedUserId).toRepresentation().isEmailVerified()); } finally { + removeUserByUsername(adminClient.realm(bc.consumerRealmName()), "consumer"); + // stop mail server + MailServer.stop(); + } + } + + @Test + public void testVerifyEmailInNewBrowserWithPreserveClient() { + //start mail server + MailServer.start(); + MailServer.createEmailAccount(USER_EMAIL, "password"); + + try { + configureSMPTServer(); + + //create user on consumer's site who should be linked later + String linkedUserId = createUser("consumer"); + + driver.navigate().to(getLoginUrl(bc.consumerRealmName(), "broker-app")); + + log.debug("Clicking social " + bc.getIDPAlias()); + accountLoginPage.clickSocial(bc.getIDPAlias()); + + waitForPage(driver, "log in to", true); + + Assert.assertTrue("Driver should be on the provider realm page right now", + driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/")); + + log.debug("Logging in"); + accountLoginPage.login(bc.getUserLogin(), bc.getUserPassword()); + + waitForPage(driver, "update account information", false); + + Assert.assertTrue(updateAccountInformationPage.isCurrent()); + Assert.assertTrue("We must be on correct realm right now", + driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/")); + + log.debug("Updating info on updateAccount page"); + updateAccountInformationPage.updateAccountInformation("Firstname", "Lastname"); + + //link account by email + waitForPage(driver, "account already exists", false); + idpConfirmLinkPage.clickLinkAccount(); + + String url = assertEmailAndGetUrl(MailServerConfiguration.FROM, USER_EMAIL, + "Someone wants to link your ", false); + + log.info("navigating to url from email in second browser: " + url); + + // navigate to url in the second browser + driver2.navigate().to(url); + + final WebElement proceedLink = driver2.findElement(By.linkText("» Click here to proceed")); + MatcherAssert.assertThat(proceedLink, Matchers.notNullValue()); + + // check if the initial client is preserved + String link = proceedLink.getAttribute("href"); + MatcherAssert.assertThat(link, Matchers.containsString("client_id=broker-app")); + proceedLink.click(); + + assertThat(driver2.getPageSource(), Matchers.containsString("You successfully verified your email. Please go back to your original browser and continue there with the login.")); + + //test if the user has verified email + assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(linkedUserId).toRepresentation().isEmailVerified()); + } finally { + removeUserByUsername(adminClient.realm(bc.consumerRealmName()), "consumer"); // stop mail server MailServer.stop(); } @@ -431,4 +503,17 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest { String link = errorPage.getBackToApplicationLink(); Assert.assertTrue(link.endsWith("/auth/realms/consumer/account")); } + + private void configureSMPTServer() { + RealmRepresentation master = adminClient.realm(bc.consumerRealmName()).toRepresentation(); + master.setSmtpServer(suiteContext.getSmtpServer()); + adminClient.realm(bc.consumerRealmName()).update(master); + } + + private String createUser(String username) { + UserRepresentation newUser = UserBuilder.create().username(username).email(USER_EMAIL).enabled(true).build(); + String userId = createUserWithAdminClient(adminClient.realm(bc.consumerRealmName()), newUser); + resetUserPassword(adminClient.realm(bc.consumerRealmName()).users().get(userId), "password", false); + return userId; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java index c6da797197..5748179fcc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java @@ -121,7 +121,20 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration { @Override public List createConsumerClients(SuiteContext suiteContext) { - return null; + ClientRepresentation client = new ClientRepresentation(); + client.setId("broker-app"); + client.setClientId("broker-app"); + client.setName("broker-app"); + client.setSecret("broker-app-secret"); + client.setEnabled(true); + + client.setRedirectUris(Collections.singletonList(getAuthRoot(suiteContext) + + "/auth/*")); + + client.setBaseUrl(getAuthRoot(suiteContext) + + "/auth/realms/" + REALM_CONS_NAME + "/app"); + + return Collections.singletonList(client); } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java index 904acd6eb2..25877d48e5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java @@ -166,6 +166,15 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration { .addRedirectUri("http://localhost:8080/sales-post/*") .attribute(SamlConfigAttributes.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE) .attribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, SamlProtocol.ATTRIBUTE_FALSE_VALUE) + .build(), + ClientBuilder.create() + .id("broker-app") + .clientId("broker-app") + .name("broker-app") + .secret("broker-app-secret") + .enabled(true) + .addRedirectUri(getAuthRoot(suiteContext) + "/auth/*") + .baseUrl(getAuthRoot(suiteContext) + "/auth/realms/" + REALM_CONS_NAME + "/app") .build() ); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java index 633494bb64..5f36170e05 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java @@ -16,6 +16,8 @@ */ package org.keycloak.testsuite.forms; +import org.hamcrest.Matchers; +import org.jboss.arquillian.drone.api.annotation.Drone; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionToken; import org.jboss.arquillian.graphene.page.Page; @@ -40,9 +42,11 @@ import org.keycloak.testsuite.pages.LoginPasswordResetPage; import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; import org.keycloak.testsuite.pages.VerifyEmailPage; import org.keycloak.testsuite.updaters.ClientAttributeUpdater; +import org.keycloak.testsuite.updaters.RealmAttributeUpdater; import org.keycloak.testsuite.util.GreenMailRule; import org.keycloak.testsuite.util.MailUtils; import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.SecondBrowser; import org.keycloak.testsuite.util.UserActionTokenBuilder; import org.keycloak.testsuite.util.UserBuilder; @@ -57,7 +61,11 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.junit.*; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; /** @@ -68,6 +76,10 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { private String userId; + @Drone + @SecondBrowser + protected WebDriver driver2; + @Override public void configureTestRealm(RealmRepresentation testRealm) { } @@ -182,7 +194,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { String changePasswordUrl = resetPassword("login-test"); events.clear(); - assertSecondPasswordResetFails(changePasswordUrl, null); // KC_RESTART doesn't exists, it was deleted after first successful reset-password flow was finished + assertSecondPasswordResetFails(changePasswordUrl, oauth.getClientId()); // KC_RESTART doesn't exists, it was deleted after first successful reset-password flow was finished } @Test @@ -194,7 +206,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { driver.navigate().to(resetUri); // This is necessary to delete KC_RESTART cookie that is restricted to /auth/realms/test path driver.manage().deleteAllCookies(); - assertSecondPasswordResetFails(changePasswordUrl, null); + assertSecondPasswordResetFails(changePasswordUrl, oauth.getClientId()); } public void assertSecondPasswordResetFails(String changePasswordUrl, String clientId) { @@ -204,7 +216,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { assertEquals("Action expired. Please continue with login now.", errorPage.getError()); events.expect(EventType.RESET_PASSWORD) - .client("account") + .client(clientId) .session((String) null) .user(userId) .error(Errors.EXPIRED_CODE) @@ -1012,4 +1024,36 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { } } + @Test + public void resetPasswordLinkNewBrowserSessionPreserveClient() throws IOException, MessagingException { + loginPage.open(); + loginPage.resetPassword(); + + resetPasswordPage.assertCurrent(); + + resetPasswordPage.changePassword("login-test"); + + loginPage.assertCurrent(); + assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage()); + + assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + String changePasswordUrl = MailUtils.getPasswordResetEmailLink(message); + + driver2.navigate().to(changePasswordUrl.trim()); + + final WebElement newPassword = driver2.findElement(By.id("password-new")); + newPassword.sendKeys("resetPassword"); + final WebElement confirmPassword = driver2.findElement(By.id("password-confirm")); + confirmPassword.sendKeys("resetPassword"); + final WebElement submit = driver2.findElement(By.cssSelector("input[type=\"submit\"]")); + submit.click(); + + assertThat(driver2.getCurrentUrl(), Matchers.containsString("client_id=test-app")); + + assertThat(driver2.getPageSource(), Matchers.containsString("Your account has been updated.")); + } + }