/* * 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.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory; import org.keycloak.common.util.Time; import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionTask; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.IDToken; import org.keycloak.testsuite.MailUtil; import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.broker.util.UserSessionStatusServlet; import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus; import org.keycloak.testsuite.pages.*; import org.keycloak.testsuite.rule.GreenMailRule; import org.keycloak.testsuite.rule.LoggingRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; import org.keycloak.util.JsonSerialization; import org.openqa.selenium.WebDriver; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.internet.MimeMessage; import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.net.URI; import java.util.List; import java.util.Set; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.*; /** * @author pedroigor */ public abstract class AbstractIdentityProviderTest { protected static final URI BASE_URI = UriBuilder.fromUri("http://localhost:8081/auth").build(); @ClassRule public static BrokerKeyCloakRule brokerServerRule = new BrokerKeyCloakRule(); @Rule public LoggingRule loggingRule = new LoggingRule(this); @Rule public WebRule webRule = new WebRule(this); @WebResource protected WebDriver driver; @WebResource protected LoginPage loginPage; @WebResource protected LoginUpdateProfilePage updateProfilePage; @WebResource protected VerifyEmailPage verifyEmailPage; @Rule public GreenMailRule greenMail = new GreenMailRule(); @WebResource protected OAuthClient oauth; @WebResource protected OAuthGrantPage grantPage; @WebResource AccountUpdateProfilePage accountUpdateProfilePage; @WebResource protected AccountPasswordPage changePasswordPage; @WebResource protected AccountFederatedIdentityPage accountFederatedIdentityPage; @WebResource protected ErrorPage errorPage; @WebResource protected InfoPage infoPage; protected KeycloakSession session; protected int logoutTimeOffset = 0; @Before public void onBefore() { this.session = brokerServerRule.startSession(); removeTestUsers(); brokerServerRule.stopSession(this.session, true); this.session = brokerServerRule.startSession(); assertNotNull(getIdentityProviderModel()); } @After public void onAfter() { revokeGrant(); brokerServerRule.stopSession(this.session, true); } protected UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail, boolean isProfileUpdateExpected) { authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected); // authenticated and redirected to app assertTrue("Bad current URL " + this.driver.getCurrentUrl() + " and page source: " + this.driver.getPageSource(), this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); UserModel federatedUser = getFederatedUser(); assertNotNull(federatedUser); assertNotNull(federatedUser.getCreatedTimestamp()); // test that timestamp is current with 10s tollerance Assert.assertTrue((System.currentTimeMillis() - federatedUser.getCreatedTimestamp()) < 10000); doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected); brokerServerRule.stopSession(session, true); session = brokerServerRule.startSession(); RealmModel realm = getRealm(); Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); assertEquals(1, federatedIdentities.size()); FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); assertEquals(federatedUser.getUsername(), federatedIdentityModel.getUserName()); // test access token timeot on logout if (logoutTimeOffset > 0) { Time.setOffset(logoutTimeOffset); } try { driver.navigate().to("http://localhost:8081/test-app/logout"); } finally { Time.setOffset(0); } driver.navigate().to("http://localhost:8081/test-app"); assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); return federatedUser; } protected void doAssertFederatedUserNoEmail(UserModel federatedUser) { assertEquals("kc-oidc-idp.test-user-noemail", federatedUser.getUsername()); assertEquals(null, federatedUser.getEmail()); assertEquals("Test", federatedUser.getFirstName()); assertEquals("User", federatedUser.getLastName()); } protected void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username, boolean isProfileUpdateExpected) { loginIDP(username); if (isProfileUpdateExpected) { String userEmail = "new@email.com"; String userFirstName = "New first"; String userLastName = "New last"; // update profile this.updateProfilePage.assertCurrent(); this.updateProfilePage.update(userFirstName, userLastName, userEmail); } } protected void loginIDP(String username) { driver.navigate().to("http://localhost:8081/test-app"); assertThat(this.driver.getCurrentUrl(), startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); // choose the identity provider this.loginPage.clickSocial(getProviderId()); String currentUrl = this.driver.getCurrentUrl(); assertThat(currentUrl, startsWith("http://localhost:8082/auth/")); // log in to identity provider this.loginPage.login(username, "password"); doAfterProviderAuthentication(); } protected void loginToIDPWhenAlreadyLoggedIntoProviderIdP(String username) { driver.navigate().to("http://localhost:8081/test-app"); assertThat(this.driver.getCurrentUrl(), startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); // choose the identity provider this.loginPage.clickSocial(getProviderId()); doAfterProviderAuthentication(); } protected UserModel getFederatedUser() { UserSessionStatus userSessionStatus = retrieveSessionStatus(); IDToken idToken = userSessionStatus.getIdToken(); KeycloakSession samlServerSession = brokerServerRule.startSession(); try { RealmModel brokerRealm = samlServerSession.realms().getRealm("realm-with-broker"); return samlServerSession.users().getUserById(idToken.getSubject(), brokerRealm); } finally { brokerServerRule.stopSession(samlServerSession, false); } } protected void doAfterProviderAuthentication() { } protected void revokeGrant() { } protected abstract String getProviderId(); protected IdentityProviderModel getIdentityProviderModel() { IdentityProviderModel identityProviderModel = getRealm().getIdentityProviderByAlias(getProviderId()); assertNotNull(identityProviderModel); identityProviderModel.setEnabled(true); return identityProviderModel; } protected RealmModel getRealm() { return getRealm(this.session); } protected static RealmModel getRealm(KeycloakSession session) { return session.realms().getRealm("realm-with-broker"); } protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel, String expectedEmail, boolean isProfileUpdateExpected) { if (isProfileUpdateExpected) { String userFirstName = "New first"; String userLastName = "New last"; assertEquals(expectedEmail, federatedUser.getEmail()); assertEquals(userFirstName, federatedUser.getFirstName()); assertEquals(userLastName, federatedUser.getLastName()); } else { assertEquals(expectedEmail, federatedUser.getEmail()); assertEquals("Test", federatedUser.getFirstName()); assertEquals("User", federatedUser.getLastName()); } } private void removeTestUsers() { RealmModel realm = getRealm(); List<UserModel> users = this.session.users().getUsers(realm, true); for (UserModel user : users) { Set<FederatedIdentityModel> identities = this.session.users().getFederatedIdentities(user, realm); for (FederatedIdentityModel fedIdentity : identities) { this.session.users().removeFederatedIdentity(realm, user, fedIdentity.getIdentityProvider()); } if (!"pedroigor".equals(user.getUsername())) { this.session.users().removeUser(realm, user); } } } protected void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) { KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), new KeycloakSessionTask() { @Override public void run(KeycloakSession session) { RealmModel realm = getRealm(session); setUpdateProfileFirstLogin(realm, updateProfileFirstLogin); } }); } protected static void setUpdateProfileFirstLogin(RealmModel realm, String updateProfileFirstLogin) { AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS); reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin); realm.updateAuthenticatorConfig(reviewProfileConfig); } protected UserSessionStatusServlet.UserSessionStatus retrieveSessionStatus() { UserSessionStatusServlet.UserSessionStatus sessionStatus = null; try { String pageSource = this.driver.getPageSource(); sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatusServlet.UserSessionStatus.class); } catch (IOException ignore) { ignore.printStackTrace(); } return sessionStatus; } protected String getVerificationEmailLink(MimeMessage message) throws IOException, MessagingException { Multipart multipart = (Multipart) message.getContent(); final String textContentType = multipart.getBodyPart(0).getContentType(); assertEquals("text/plain; charset=UTF-8", textContentType); final String textBody = (String) multipart.getBodyPart(0).getContent(); final String textVerificationUrl = MailUtil.getLink(textBody); final String htmlContentType = multipart.getBodyPart(1).getContentType(); assertEquals("text/html; charset=UTF-8", htmlContentType); final String htmlBody = (String) multipart.getBodyPart(1).getContent(); final String htmlVerificationUrl = MailUtil.getLink(htmlBody); assertEquals(htmlVerificationUrl, textVerificationUrl); return htmlVerificationUrl; } }