From 8a0f1ccb34ac7969bd1f730f87e6b1a0245b8ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Wed, 23 Feb 2022 09:11:03 +0100 Subject: [PATCH] Properly execute AuthenticationFlowCallbackProviderTest with Map storage Closes #10268, Closes #10225 --- .../keycloak/testsuite/pages/PageUtils.java | 7 +- .../testsuite/AbstractKeycloakTest.java | 23 +++++- ...uthenticationFlowCallbackProviderTest.java | 27 ++++--- .../org/keycloak/testsuite/util/FlowUtil.java | 77 +++++++++++++++++-- 4 files changed, 113 insertions(+), 21 deletions(-) diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/PageUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/PageUtils.java index 5612e5a949..632a213776 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/PageUtils.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/PageUtils.java @@ -1,12 +1,17 @@ package org.keycloak.testsuite.pages; import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; public class PageUtils { public static String getPageTitle(WebDriver driver) { - return driver.findElement(By.id("kc-page-title")).getText(); + try { + return driver.findElement(By.id("kc-page-title")).getText(); + } catch (NoSuchElementException e) { + return null; + } } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index da897a6d22..6407d3a75e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -81,9 +81,9 @@ import java.util.Scanner; import java.util.concurrent.*; import java.util.function.Consumer; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; import static org.keycloak.testsuite.admin.Users.setPasswordFor; import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST; import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_PORT; @@ -188,12 +188,29 @@ public abstract class AbstractKeycloakTest { adminClient = testContext.getAdminClient(); } + /** + * Executed before test realms import + *

+ * In @Before block + */ protected void beforeAbstractKeycloakTestRealmImport() throws Exception { } - protected void postAfterAbstractKeycloak() throws Exception { + + /** + * Executed after test realms import + *

+ * In @Before block + */ + protected void afterAbstractKeycloakTestRealmImport() { } - protected void afterAbstractKeycloakTestRealmImport() {} + /** + * Executed as the last task of each test case + *

+ * In @After block + */ + protected void postAfterAbstractKeycloak() throws Exception { + } @After public void afterAbstractKeycloakTest() throws Exception { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AuthenticationFlowCallbackProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AuthenticationFlowCallbackProviderTest.java index f023661b0d..14b32607d1 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AuthenticationFlowCallbackProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AuthenticationFlowCallbackProviderTest.java @@ -18,12 +18,13 @@ package org.keycloak.testsuite.forms; import org.jboss.arquillian.graphene.page.Page; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.keycloak.authentication.authenticators.access.AllowAccessAuthenticatorFactory; import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory; import org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticator; import org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticatorFactory; -import org.keycloak.common.Profile; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; @@ -36,7 +37,6 @@ import org.keycloak.testsuite.util.FlowUtil; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assume.assumeThat; import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE; /** @@ -45,6 +45,8 @@ import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerEx @AuthServerContainerExclude(REMOTE) public class AuthenticationFlowCallbackProviderTest extends AbstractTestRealmKeycloakTest { + protected static final String DEFAULT_FLOW = "newCallbackFlow"; + @Page protected LoginPage loginPage; @@ -55,9 +57,18 @@ public class AuthenticationFlowCallbackProviderTest extends AbstractTestRealmKey public void configureTestRealm(RealmRepresentation testRealm) { } + @Before + public void setUpFlow() { + setBrowserFlow(); + } + + @After + public void revertFlow() { + BrowserFlowTest.revertFlows(testRealm(), DEFAULT_FLOW); + } + @Test public void loaEssentialNonExisting() { - setBrowserFlow(); LevelOfAssuranceFlowTest.openLoginFormWithAcrClaim(oauth, true, "4"); loginPage.assertCurrent(); @@ -69,10 +80,6 @@ public class AuthenticationFlowCallbackProviderTest extends AbstractTestRealmKey @Test public void errorWithCustomProvider() { - // Ignore test case for Map Storage - GitHub Issue #10225 - assumeThat("This test case does not work properly with Map Storage", Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE), is(false)); - - setBrowserFlow(); LevelOfAssuranceFlowTest.openLoginFormWithAcrClaim(oauth, true, "1"); loginPage.assertCurrent(); @@ -83,9 +90,9 @@ public class AuthenticationFlowCallbackProviderTest extends AbstractTestRealmKey } protected void setBrowserFlow() { - testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow("newFlow")); - testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session) - .selectFlow("newFlow") + testingClient.server(TEST_REALM_NAME).run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(DEFAULT_FLOW)); + testingClient.server(TEST_REALM_NAME).run(session -> FlowUtil.inCurrentRealm(session) + .selectFlow(DEFAULT_FLOW) .inForms(forms -> forms .clear() .addSubFlowExecution(AuthenticationExecutionModel.Requirement.CONDITIONAL, subflow -> subflow diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java index fec05fc609..6c47e75754 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java @@ -8,16 +8,24 @@ import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.services.resources.admin.AuthenticationManagementResource; import java.util.HashMap; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; +import static org.keycloak.models.utils.DefaultAuthenticationFlows.BROWSER_FLOW; +import static org.keycloak.models.utils.DefaultAuthenticationFlows.DIRECT_GRANT_FLOW; +import static org.keycloak.models.utils.DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW; +import static org.keycloak.models.utils.DefaultAuthenticationFlows.REGISTRATION_FLOW; +import static org.keycloak.models.utils.DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW; + public class FlowUtil { private RealmModel realm; private AuthenticationFlowModel currentFlow; @@ -67,23 +75,27 @@ public class FlowUtil { } public FlowUtil copyBrowserFlow(String newFlowAlias) { - return copyFlow(DefaultAuthenticationFlows.BROWSER_FLOW, newFlowAlias); + checkAndRestoreDefaultFlow(realm::getBrowserFlow, realm::setBrowserFlow, newFlowAlias, BROWSER_FLOW); + return copyFlow(BROWSER_FLOW, newFlowAlias); } public FlowUtil copyResetCredentialsFlow(String newFlowAlias) { - return copyFlow(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, newFlowAlias); + checkAndRestoreDefaultFlow(realm::getResetCredentialsFlow, realm::setResetCredentialsFlow, newFlowAlias, RESET_CREDENTIALS_FLOW); + return copyFlow(RESET_CREDENTIALS_FLOW, newFlowAlias); } public FlowUtil copyFirstBrokerLoginFlow(String newFlowAlias) { - return copyFlow(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, newFlowAlias); + return copyFlow(FIRST_BROKER_LOGIN_FLOW, newFlowAlias); } public FlowUtil copyRegistrationFlow(String newFlowAlias) { - return copyFlow(DefaultAuthenticationFlows.REGISTRATION_FLOW, newFlowAlias); + checkAndRestoreDefaultFlow(realm::getRegistrationFlow, realm::setRegistrationFlow, newFlowAlias, REGISTRATION_FLOW); + return copyFlow(REGISTRATION_FLOW, newFlowAlias); } public FlowUtil copyDirectGrantFlow(String newFlowAlias) { - return copyFlow(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, newFlowAlias); + checkAndRestoreDefaultFlow(realm::getDirectGrantFlow, realm::setDirectGrantFlow, newFlowAlias, DIRECT_GRANT_FLOW); + return copyFlow(DIRECT_GRANT_FLOW, newFlowAlias); } public FlowUtil copyFlow(String original, String newFlowAlias) { @@ -92,6 +104,14 @@ public class FlowUtil { if (existingBrowserFlow == null) { throw new FlowUtilException("Can't copy flow: " + original + " does not exist"); } + + // remove new authentication flow with 'newFlowAlias' alias if present + AuthenticationFlowModel foundFlow = realm.getFlowByAlias(newFlowAlias); + if (foundFlow != null) { + clearAuthenticationFlow(foundFlow.getId()); + realm.removeAuthenticationFlow(foundFlow); + } + currentFlow = AuthenticationManagementResource.copyFlow(realm, existingBrowserFlow, newFlowAlias); return this; @@ -119,7 +139,7 @@ public class FlowUtil { } public FlowUtil clear() { - realm.getAuthenticationExecutionsStream(currentFlow.getId()).forEachOrdered(realm::removeAuthenticatorExecution); + clearAuthenticationFlow(currentFlow.getId()); return this; } @@ -264,4 +284,47 @@ public class FlowUtil { return this; } + + /** + * Remove authentication flows and executions included in the specified flow + * + * @param flowId id of flow, which content will be removed + */ + private void clearAuthenticationFlow(String flowId) { + realm.getAuthenticationExecutionsStream(flowId) + .filter(Objects::nonNull) + .forEachOrdered(f -> { + if (f.isAuthenticatorFlow() && f.getFlowId() != null) { + clearAuthenticationFlow(f.getFlowId()); + realm.removeAuthenticationFlow(realm.getAuthenticationFlowById(f.getFlowId())); + } + realm.removeAuthenticatorExecution(f); + }); + } + + /** + * Check whether the new flow is set as default one + * If yes, restore the default one + *

+ * Usable for removing flow, which must not be used as the default flow + * + * @param getFlow getter for obtaining the default flow + * @param setFlow setter for the setting of the default flow + * @param newFlowAlias alias of tested flow + * @param defaultFlowAlias default flow alias + */ + private void checkAndRestoreDefaultFlow(Supplier getFlow, + Consumer setFlow, + String newFlowAlias, + String defaultFlowAlias) { + if (getFlow == null || setFlow == null || newFlowAlias == null || defaultFlowAlias == null) return; + + final String alias = Optional.ofNullable(getFlow.get()) + .map(AuthenticationFlowModel::getAlias) + .orElse(null); + + if (alias != null && alias.equals(newFlowAlias)) { + setFlow.accept(realm.getFlowByAlias(defaultFlowAlias)); + } + } } \ No newline at end of file