/* * 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; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.Time; import org.keycloak.testsuite.arquillian.KcArquillian; import org.keycloak.testsuite.arquillian.TestContext; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import javax.ws.rs.NotFoundException; import org.jboss.arquillian.container.test.api.RunAsClient; import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.logging.Logger; import org.junit.After; import org.junit.Before; import org.junit.runner.RunWith; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.AuthenticationManagementResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmsResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UsersResource; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import org.keycloak.testsuite.arquillian.SuiteContext; import org.keycloak.testsuite.auth.page.AuthRealm; import org.keycloak.testsuite.auth.page.AuthServer; import org.keycloak.testsuite.auth.page.AuthServerContextRoot; import org.keycloak.testsuite.auth.page.WelcomePage; import org.keycloak.testsuite.auth.page.account.Account; import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.auth.page.login.UpdatePassword; import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.util.AdminClientUtil; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.TestCleanup; import org.keycloak.testsuite.util.TestEventsLogger; import org.openqa.selenium.WebDriver; import static org.keycloak.testsuite.admin.Users.setPasswordFor; import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN; import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; /** * * @author tkyjovsk */ @RunWith(KcArquillian.class) @RunAsClient public abstract class AbstractKeycloakTest { protected Logger log = Logger.getLogger(this.getClass()); @ArquillianResource protected SuiteContext suiteContext; @ArquillianResource protected TestContext testContext; protected Keycloak adminClient; protected KeycloakTestingClient testingClient; @ArquillianResource protected OAuthClient oauth; protected List<RealmRepresentation> testRealmReps; @Drone protected WebDriver driver; @Page protected AuthServerContextRoot authServerContextRootPage; @Page protected AuthServer authServerPage; @Page protected AuthRealm masterRealmPage; @Page protected Account accountPage; @Page protected OIDCLogin loginPage; @Page protected UpdatePassword updatePasswordPage; @Page protected WelcomePage welcomePage; protected UserRepresentation adminUser; private PropertiesConfiguration constantsProperties; private boolean resetTimeOffset; @Before public void beforeAbstractKeycloakTest() throws Exception { adminClient = testContext.getAdminClient(); if (adminClient == null) { String authServerContextRoot = suiteContext.getAuthServerInfo().getContextRoot().toString(); adminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), authServerContextRoot); testContext.setAdminClient(adminClient); } getTestingClient(); adminUser = createAdminUserRepresentation(); setDefaultPageUriParameters(); TestEventsLogger.setDriver(driver); // The backend cluster nodes may not be yet started. Password will be updated later for cluster setup. if (!AuthServerTestEnricher.AUTH_SERVER_CLUSTER) { updateMasterAdminPassword(); } if (testContext.getTestRealmReps() == null) { importTestRealms(); if (!isImportAfterEachMethod()) { testContext.setTestRealmReps(testRealmReps); } } oauth.init(adminClient, driver); } @After public void afterAbstractKeycloakTest() { if (resetTimeOffset) { resetTimeOffset(); } if (isImportAfterEachMethod()) { log.info("removing test realms after test method"); for (RealmRepresentation testRealm : testRealmReps) { removeRealm(testRealm.getRealm()); } } else { // Logout all users after the test List<RealmRepresentation> realms = testContext.getTestRealmReps(); for (RealmRepresentation realm : realms) { adminClient.realm(realm.getRealm()).logoutAll(); } // Cleanup objects for (TestCleanup cleanup : testContext.getCleanups().values()) { cleanup.executeCleanup(); } testContext.getCleanups().clear(); } } protected TestCleanup getCleanup(String realmName) { return testContext.getOrCreateCleanup(realmName); } protected TestCleanup getCleanup() { return getCleanup("test"); } protected boolean isImportAfterEachMethod() { return false; } protected void updateMasterAdminPassword() { if (!suiteContext.isAdminPasswordUpdated()) { log.debug("updating admin password"); welcomePage.navigateTo(); if (!welcomePage.isPasswordSet()) { welcomePage.setPassword("admin", "admin"); } suiteContext.setAdminPasswordUpdated(true); } } public void deleteAllCookiesForMasterRealm() { deleteAllCookiesForRealm(accountPage); } protected void deleteAllCookiesForRealm(Account realmAccountPage) { // masterRealmPage.navigateTo(); realmAccountPage.navigateTo(); // Because IE webdriver freezes when loading a JSON page (realm page), we need to use this alternative log.info("deleting cookies in '" + realmAccountPage.getAuthRealm() + "' realm"); driver.manage().deleteAllCookies(); } protected void deleteAllCookiesForRealm(String realmName) { // masterRealmPage.navigateTo(); driver.navigate().to(oauth.AUTH_SERVER_ROOT + "/realms/" + realmName + "/account"); // Because IE webdriver freezes when loading a JSON page (realm page), we need to use this alternative log.info("deleting cookies in '" + realmName + "' realm"); driver.manage().deleteAllCookies(); } public void setDefaultPageUriParameters() { masterRealmPage.setAuthRealm(MASTER); loginPage.setAuthRealm(MASTER); } public KeycloakTestingClient getTestingClient() { if (testingClient == null) { testingClient = testContext.getTestingClient(); if (testingClient == null) { String authServerContextRoot = suiteContext.getAuthServerInfo().getContextRoot().toString(); testingClient = KeycloakTestingClient.getInstance(authServerContextRoot + "/auth"); testContext.setTestingClient(testingClient); } } return testingClient; } public TestContext getTestContext() { return testContext; } public Keycloak getAdminClient() { return adminClient; } public abstract void addTestRealms(List<RealmRepresentation> testRealms); private void addTestRealms() { log.debug("loading test realms"); if (testRealmReps == null) { testRealmReps = new ArrayList<>(); } if (testRealmReps.isEmpty()) { addTestRealms(testRealmReps); } } public void importTestRealms() { addTestRealms(); log.info("importing test realms"); for (RealmRepresentation testRealm : testRealmReps) { importRealm(testRealm); } } private UserRepresentation createAdminUserRepresentation() { UserRepresentation adminUserRep = new UserRepresentation(); adminUserRep.setUsername(ADMIN); setPasswordFor(adminUserRep, ADMIN); return adminUserRep; } public void importRealm(RealmRepresentation realm) { log.debug("importing realm: " + realm.getRealm()); try { // TODO - figure out a way how to do this without try-catch RealmResource realmResource = adminClient.realms().realm(realm.getRealm()); RealmRepresentation rRep = realmResource.toRepresentation(); log.debug("realm already exists on server, re-importing"); realmResource.remove(); } catch (NotFoundException nfe) { // expected when realm does not exist } adminClient.realms().create(realm); } public void removeRealm(String realmName) { log.info("removing realm: " + realmName); try { adminClient.realms().realm(realmName).remove(); } catch (NotFoundException e) { } } public RealmsResource realmsResouce() { return adminClient.realms(); } /** * Creates a user in the given realm and returns its ID. * @param realm Realm name * @param username Username * @param password Password * @param requiredActions * @return ID of the newly created user */ public String createUser(String realm, String username, String password, String ... requiredActions) { List<String> requiredUserActions = Arrays.asList(requiredActions); UserRepresentation homer = new UserRepresentation(); homer.setEnabled(true); homer.setUsername(username); homer.setRequiredActions(requiredUserActions); return ApiUtil.createUserAndResetPasswordWithAdminClient(adminClient.realm(realm), homer, password); } public void setRequiredActionEnabled(String realm, String requiredAction, boolean enabled, boolean defaultAction) { AuthenticationManagementResource managementResource = adminClient.realm(realm).flows(); RequiredActionProviderRepresentation action = managementResource.getRequiredAction(requiredAction); action.setEnabled(enabled); action.setDefaultAction(defaultAction); managementResource.updateRequiredAction(requiredAction, action); } public void setRequiredActionEnabled(String realm, String userId, String requiredAction, boolean enabled) { UsersResource usersResource = adminClient.realm(realm).users(); UserResource userResource = usersResource.get(userId); UserRepresentation userRepresentation = userResource.toRepresentation(); List<String> requiredActions = userRepresentation.getRequiredActions(); if (enabled && !requiredActions.contains(requiredAction)) { requiredActions.add(requiredAction); } else if (!enabled && requiredActions.contains(requiredAction)) { requiredActions.remove(requiredAction); } userResource.update(userRepresentation); } /** * Sets time offset in seconds that will be added to Time.currentTime() and Time.currentTimeMillis() both for client and server. * @param offset */ public void setTimeOffset(int offset) { String response = invokeTimeOffset(offset); resetTimeOffset = offset != 0; log.debugv("Set time offset, response {0}", response); } public void resetTimeOffset() { String response = invokeTimeOffset(0); resetTimeOffset = false; log.debugv("Reset time offset, response {0}", response); } public int getCurrentTime() { return Time.currentTime(); } private String invokeTimeOffset(int offset) { // adminClient depends on Time.offset for auto-refreshing tokens Time.setOffset(offset); Map result = testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(offset))); return String.valueOf(result); } private void loadConstantsProperties() throws ConfigurationException { constantsProperties = new PropertiesConfiguration(System.getProperty("testsuite.constants")); constantsProperties.setThrowExceptionOnMissing(true); } protected PropertiesConfiguration getConstantsProperties() throws ConfigurationException { if (constantsProperties == null) { loadConstantsProperties(); } return constantsProperties; } public URI getAuthServerRoot() { try { return KeycloakUriBuilder.fromUri(suiteContext.getAuthServerInfo().getContextRoot().toURI()).path("/auth/").build(); } catch (URISyntaxException e) { throw new RuntimeException(e); } } }