/*
* Copyright 2016 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.broker;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory;
import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
import org.keycloak.testsuite.pages.IdpLinkEmailPage;
import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.LoginExpiredPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import javax.mail.internet.MimeMessage;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProviderTest {
protected static final String APP_REALM_ID = "realm-with-broker";
@WebResource
protected LoginUpdateProfileEditUsernameAllowedPage updateProfileWithUsernamePage;
@WebResource
protected IdpConfirmLinkPage idpConfirmLinkPage;
@WebResource
protected IdpLinkEmailPage idpLinkEmailPage;
@WebResource
protected LoginPasswordUpdatePage passwordUpdatePage;
@WebResource
protected LoginExpiredPage loginExpiredPage;
/**
* Tests that if updateProfile is off and CreateUserIfUnique authenticator mandatory, error page will be shown if user with same email already exists
*/
@Test
public void testErrorPageWhenDuplicationNotAllowed_updateProfileOff() {
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.REQUIRED);
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
}
}, APP_REALM_ID);
loginIDP("pedroigor");
WebElement element = this.driver.findElement(By.className("instruction"));
assertNotNull(element);
assertEquals("User with email psilva@redhat.com already exists. Please login to account management to link the account.", element.getText());
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
}
}, APP_REALM_ID);
}
/**
* Tests that if updateProfile is on and CreateUserIfUnique authenticator mandatory, error page will be shown if user with same email already exists
*/
@Test
public void testErrorPageWhenDuplicationNotAllowed_updateProfileOn() {
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.REQUIRED);
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_ON);
}
}, APP_REALM_ID);
loginIDP("test-user");
this.updateProfileWithUsernamePage.assertCurrent();
this.updateProfileWithUsernamePage.update("Test", "User", "test-user@redhat.com", "pedroigor");
WebElement element = this.driver.findElement(By.className("instruction"));
assertNotNull(element);
assertEquals("User with username pedroigor already exists. Please login to account management to link the account.", element.getText());
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
}
}, APP_REALM_ID);
}
/**
* Test user registers with IdentityProvider and needs to update password when it's required by IdpCreateUserIfUniqueAuthenticator
*/
@Test
public void testRegistrationWithPasswordUpdateRequired() {
// Require updatePassword after user registered with broker
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
AuthenticatorConfigModel authenticatorConfig = realmWithBroker.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
authenticatorConfig.getConfig().put(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
realmWithBroker.updateAuthenticatorConfig(authenticatorConfig);
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_MISSING);
}
}, APP_REALM_ID);
loginIDP("pedroigor");
this.updateProfileWithUsernamePage.assertCurrent();
this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com", "some-user");
// Need to update password now
this.passwordUpdatePage.assertCurrent();
this.passwordUpdatePage.changePassword("password1", "password1");
// assert authenticated
assertFederatedUser("some-user", "some-user@redhat.com", "pedroigor");
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
AuthenticatorConfigModel authenticatorConfig = realmWithBroker.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
authenticatorConfig.getConfig().put(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "false");
realmWithBroker.updateAuthenticatorConfig(authenticatorConfig);
}
}, APP_REALM_ID);
}
/**
* Test user registers with IdentityProvider with emailAsUsername
*/
@Test
public void testRegistrationWithEmailAsUsername() {
// Require updatePassword after user registered with broker
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_ON);
realmWithBroker.setRegistrationEmailAsUsername(true);
}
}, APP_REALM_ID);
loginIDP("pedroigor");
this.updateProfileWithUsernamePage.assertCurrent();
try {
this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com", "some-user");
Assert.fail("It is not expected to see username field");
} catch (NoSuchElementException expected) {
}
this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com");
// assert authenticated
assertFederatedUser("some-user@redhat.com", "some-user@redhat.com", "pedroigor");
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_MISSING);
realmWithBroker.setRegistrationEmailAsUsername(false);
}
}, APP_REALM_ID);
}
/**
* Tests that duplication is detected, the confirmation page is displayed, user clicks on "Review profile" and goes back to updateProfile page and resolves duplication
* by create new user
*/
@Test
public void testFixDuplicationsByReviewProfile() {
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
loginIDP("pedroigor");
// There is user with same email. Update profile to use different email
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickReviewProfile();
this.updateProfileWithUsernamePage.assertCurrent();
this.updateProfileWithUsernamePage.update("Test", "User", "testing-user@redhat.com", "pedroigor");
// There is user with same username. Update profile to use different username
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with username pedroigor already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickReviewProfile();
this.updateProfileWithUsernamePage.assertCurrent();
this.updateProfileWithUsernamePage.update("Test", "User", "testing-user@redhat.com", "testing-user");
assertFederatedUser("testing-user", "testing-user@redhat.com", "pedroigor");
}
/**
* Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by email
*/
@Test
public void testLinkAccountByEmailVerification() throws Exception {
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
// Confirm linking account by email
this.idpLinkEmailPage.assertCurrent();
Assert.assertEquals("An email with instructions to link " + ObjectUtil.capitalize(getProviderId()) + " account pedroigor with your " + APP_REALM_ID + " account has been sent to you.", this.idpLinkEmailPage.getMessage());
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String linkFromMail = getVerificationEmailLink(message);
driver.navigate().to(linkFromMail.trim());
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
// Assert user's email is verified now
UserModel user = getFederatedUser();
Assert.assertTrue(user.isEmailVerified());
}
/**
* Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by email
*/
@Test
public void testLinkAccountByEmailVerificationTwice() throws Exception {
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
// Confirm linking account by email
this.idpLinkEmailPage.assertCurrent();
Assert.assertThat(
this.idpLinkEmailPage.getMessage(),
is("An email with instructions to link " + ObjectUtil.capitalize(getProviderId()) + " account pedroigor with your " + APP_REALM_ID + " account has been sent to you.")
);
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String linkFromMail = getVerificationEmailLink(message);
driver.navigate().to(linkFromMail.trim());
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
// Assert user's email is verified now
UserModel user = getFederatedUser();
Assert.assertTrue(user.isEmailVerified());
// Attempt to use the link for the second time
driver.navigate().to(linkFromMail.trim());
infoPage.assertCurrent();
Assert.assertThat(infoPage.getInfo(), is("You are already logged in."));
// Log out
driver.navigate().to("http://localhost:8081/test-app/logout");
// Go to the same link again
driver.navigate().to(linkFromMail.trim());
infoPage.assertCurrent();
Assert.assertThat(infoPage.getInfo(), startsWith("You successfully verified your email. Please go back to your original browser and continue there with the login."));
}
/**
* Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by email
*/
@Test
public void testLinkAccountByEmailVerificationDifferentBrowser() throws Exception, Throwable {
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
// Confirm linking account by email
this.idpLinkEmailPage.assertCurrent();
Assert.assertThat(
this.idpLinkEmailPage.getMessage(),
is("An email with instructions to link " + ObjectUtil.capitalize(getProviderId()) + " account pedroigor with your " + APP_REALM_ID + " account has been sent to you.")
);
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String linkFromMail = getVerificationEmailLink(message);
WebRule webRule2 = new WebRule(this);
try {
webRule2.initProperties();
WebDriver driver2 = webRule2.getDriver();
InfoPage infoPage2 = webRule2.getPage(InfoPage.class);
driver2.navigate().to(linkFromMail.trim());
// authenticated, but not redirected to app. Just seeing info page.
infoPage2.assertCurrent();
Assert.assertThat(infoPage2.getInfo(), startsWith("You successfully verified your email. Please go back to your original browser and continue there with the login."));
} finally {
// Revert everything
webRule2.after();
}
this.idpLinkEmailPage.clickContinueFlowLink();
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
// Assert user's email is verified now
UserModel user = getFederatedUser();
Assert.assertTrue(user.isEmailVerified());
}
@Test
public void testLinkAccountByEmailVerificationResendEmail() throws Exception, Throwable {
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
// Confirm linking account by email
this.idpLinkEmailPage.assertCurrent();
Assert.assertThat(
this.idpLinkEmailPage.getMessage(),
is("An email with instructions to link " + ObjectUtil.capitalize(getProviderId()) + " account pedroigor with your " + APP_REALM_ID + " account has been sent to you.")
);
this.idpLinkEmailPage.clickResendEmail();
this.idpLinkEmailPage.assertCurrent();
Assert.assertThat(
this.idpLinkEmailPage.getMessage(),
is("An email with instructions to link " + ObjectUtil.capitalize(getProviderId()) + " account pedroigor with your " + APP_REALM_ID + " account has been sent to you.")
);
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String linkFromMail = getVerificationEmailLink(message);
driver.navigate().to(linkFromMail.trim());
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
// Assert user's email is verified now
UserModel user = getFederatedUser();
Assert.assertTrue(user.isEmailVerified());
}
/**
* Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
*/
@Test
public void testLinkAccountByReauthenticationWithPassword() throws Exception {
// Remove smtp config. The reauthentication by username+password screen will be automatically used then
final Map<String, String> smtpConfig = new HashMap<>();
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
smtpConfig.putAll(realmWithBroker.getSmtpConfig());
realmWithBroker.setSmtpConfig(Collections.<String, String>emptyMap());
}
}, APP_REALM_ID);
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
// Login screen shown. Username is prefilled and disabled. Registration link and social buttons are not shown
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
Assert.assertEquals("pedroigor", this.loginPage.getUsername());
Assert.assertFalse(this.loginPage.isUsernameInputEnabled());
Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getInfoMessage());
try {
this.loginPage.findSocialButton(getProviderId());
Assert.fail("Not expected to see social button with " + getProviderId());
} catch (NoSuchElementException expected) {
}
try {
this.loginPage.clickRegister();
Assert.fail("Not expected to see register link");
} catch (NoSuchElementException expected) {
}
// Use bad password first
this.loginPage.login("password1");
Assert.assertEquals("Invalid username or password.", this.loginPage.getError());
// Use correct password now
this.loginPage.login("password");
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
// Restore smtp config
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
realmWithBroker.setSmtpConfig(smtpConfig);
}
}, APP_REALM_ID);
}
/**
* Variation of previous test, which uses browser buttons (back, refresh etc)
*/
@Test
public void testLinkAccountByReauthenticationWithPassword_browserButtons() throws Exception {
// Remove smtp config. The reauthentication by username+password screen will be automatically used then
final Map<String, String> smtpConfig = new HashMap<>();
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
smtpConfig.putAll(realmWithBroker.getSmtpConfig());
realmWithBroker.setSmtpConfig(Collections.<String, String>emptyMap());
}
}, APP_REALM_ID);
// Use invalid username for the first time
loginIDP("foo");
assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
this.loginPage.login("pedroigor", "password");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
// Click browser 'back' and then 'forward' and then continue
driver.navigate().back();
Assert.assertTrue(driver.getPageSource().contains("You are already logged in."));
driver.navigate().forward();
this.loginExpiredPage.assertCurrent();
this.loginExpiredPage.clickLoginContinueLink();
this.idpConfirmLinkPage.assertCurrent();
// Click browser 'back' on review profile page
this.idpConfirmLinkPage.clickReviewProfile();
this.updateProfilePage.assertCurrent();
driver.navigate().back();
this.loginExpiredPage.assertCurrent();
this.loginExpiredPage.clickLoginContinueLink();
this.updateProfilePage.assertCurrent();
this.updateProfilePage.update("Pedro", "Igor", "psilva@redhat.com");
this.idpConfirmLinkPage.assertCurrent();
this.idpConfirmLinkPage.clickLinkAccount();
// Login screen shown. Username is prefilled and disabled. Registration link and social buttons are not shown
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
Assert.assertEquals("pedroigor", this.loginPage.getUsername());
Assert.assertFalse(this.loginPage.isUsernameInputEnabled());
Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getInfoMessage());
try {
this.loginPage.findSocialButton(getProviderId());
Assert.fail("Not expected to see social button with " + getProviderId());
} catch (NoSuchElementException expected) {
}
try {
this.loginPage.clickRegister();
Assert.fail("Not expected to see register link");
} catch (NoSuchElementException expected) {
}
// Use bad password first
this.loginPage.login("password1");
Assert.assertEquals("Invalid username or password.", this.loginPage.getError());
// Click browser 'back' and then continue
this.driver.navigate().back();
this.loginExpiredPage.assertCurrent();
this.loginExpiredPage.clickLoginContinueLink();
// Use correct password now
this.loginPage.login("password");
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
// Restore smtp config
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
realmWithBroker.setSmtpConfig(smtpConfig);
}
}, APP_REALM_ID);
}
/**
* Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
* and additionally he goes through "forget password"
*/
@Test
public void testLinkAccountByReauthentication_forgetPassword() throws Exception {
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
}
}, APP_REALM_ID);
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
// Click "Forget password" on login page. Email sent directly because username is known
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
this.loginPage.resetPassword();
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
Assert.assertEquals("You should receive an email shortly with further instructions.", this.loginPage.getSuccessMessage());
// Click on link from email
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String linkFromMail = getVerificationEmailLink(message);
driver.navigate().to(linkFromMail.trim());
// Need to update password now
this.passwordUpdatePage.assertCurrent();
this.passwordUpdatePage.changePassword("password", "password");
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
}
}, APP_REALM_ID);
}
/**
* Same like above, but "forget password" link is opened in different browser
*/
@Test
public void testLinkAccountByReauthentication_forgetPassword_differentBrowser() throws Throwable {
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
}
}, APP_REALM_ID);
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
// Click "Forget password" on login page. Email sent directly because username is known
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
this.loginPage.resetPassword();
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
Assert.assertEquals("You should receive an email shortly with further instructions.", this.loginPage.getSuccessMessage());
// Click on link from email
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String linkFromMail = getVerificationEmailLink(message);
// Simulate 2nd browser
WebRule webRule2 = new WebRule(this);
try {
webRule2.initProperties();
WebDriver driver2 = webRule2.getDriver();
LoginPasswordUpdatePage passwordUpdatePage2 = webRule2.getPage(LoginPasswordUpdatePage.class);
InfoPage infoPage2 = webRule2.getPage(InfoPage.class);
driver2.navigate().to(linkFromMail.trim());
// Need to update password now
passwordUpdatePage2.assertCurrent();
passwordUpdatePage2.changePassword("password", "password");
// authenticated, but not redirected to app. Just seeing info page.
infoPage2.assertCurrent();
Assert.assertEquals("Your account has been updated.", infoPage2.getInfo());
} finally {
// Revert everything
webRule2.after();
}
// User is not yet linked with identity provider. He needs to authenticate again in 1st browser
RealmModel realmWithBroker = getRealm();
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(this.session.users().getUserByUsername("pedroigor", realmWithBroker), realmWithBroker);
assertEquals(0, federatedIdentities.size());
// Continue with 1st browser. Note that the user has already authenticated with brokered IdP in the beginning of this test
// so entering their credentials there is now skipped.
loginToIDPWhenAlreadyLoggedIntoProviderIdP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
this.loginPage.login("password");
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
}
}, APP_REALM_ID);
}
protected void assertFederatedUser(String expectedUsername, String expectedEmail, String expectedFederatedUsername) {
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
UserModel federatedUser = getFederatedUser();
assertNotNull(federatedUser);
assertEquals(expectedUsername, federatedUser.getUsername());
assertEquals(expectedEmail, federatedUser.getEmail());
RealmModel realmWithBroker = getRealm();
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker);
assertEquals(1, federatedIdentities.size());
FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
assertEquals(expectedFederatedUsername, federatedIdentityModel.getUserName());
}
protected static void setExecutionRequirement(RealmModel realmWithBroker, String flowAlias, String authenticatorProvider, AuthenticationExecutionModel.Requirement requirement) {
AuthenticationFlowModel flowModel = realmWithBroker.getFlowByAlias(flowAlias);
List<AuthenticationExecutionModel> authExecutions = realmWithBroker.getAuthenticationExecutions(flowModel.getId());
for (AuthenticationExecutionModel execution : authExecutions) {
if (execution.getAuthenticator().equals(authenticatorProvider)) {
execution.setRequirement(requirement);
realmWithBroker.updateAuthenticatorExecution(execution);
return;
}
}
throw new IllegalStateException("Execution not found for flow " + flowAlias + " and authenticator " + authenticatorProvider);
}
}