diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java index 70013c57a8..fb7e966156 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java @@ -21,6 +21,7 @@ import org.junit.Assert; import org.junit.Before; import org.keycloak.admin.client.resource.AuthenticationManagementResource; import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; @@ -92,14 +93,41 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest { Assert.assertEquals("Execution requirement choices - " + actual.getProviderId(), expected.getRequirementChoices(), actual.getRequirementChoices()); } + void compareExecution(AuthenticationExecutionExportRepresentation expected, AuthenticationExecutionExportRepresentation actual) { + Assert.assertEquals("Execution flowAlias - " + actual.getAuthenticator(), expected.getFlowAlias(), actual.getFlowAlias()); + Assert.assertEquals("Execution authenticator - " + actual.getAuthenticator(), expected.getAuthenticator(), actual.getAuthenticator()); + Assert.assertEquals("Execution userSetupAllowed - " + actual.getAuthenticator(), expected.isUserSetupAllowed(), actual.isUserSetupAllowed()); + Assert.assertEquals("Execution authenticatorFlow - " + actual.getAuthenticator(), expected.isAutheticatorFlow(), actual.isAutheticatorFlow()); + Assert.assertEquals("Execution authenticatorConfig - " + actual.getAuthenticator(), expected.getAuthenticatorConfig(), actual.getAuthenticatorConfig()); + Assert.assertEquals("Execution priority - " + actual.getAuthenticator(), expected.getPriority(), actual.getPriority()); + Assert.assertEquals("Execution requirement - " + actual.getAuthenticator(), expected.getRequirement(), actual.getRequirement()); + } + + void compareExecutions(List expected, List actual) { + Assert.assertNotNull("Executions should not be null", actual); + Assert.assertEquals("Size", expected.size(), actual.size()); + + for (int i = 0; i < expected.size(); i++) { + compareExecution(expected.get(i), actual.get(i)); + } + } + void compareFlows(AuthenticationFlowRepresentation expected, AuthenticationFlowRepresentation actual) { Assert.assertEquals("Flow alias", expected.getAlias(), actual.getAlias()); Assert.assertEquals("Flow description", expected.getDescription(), actual.getDescription()); Assert.assertEquals("Flow providerId", expected.getProviderId(), actual.getProviderId()); Assert.assertEquals("Flow top level", expected.isTopLevel(), actual.isTopLevel()); Assert.assertEquals("Flow built-in", expected.isBuiltIn(), actual.isBuiltIn()); - } + List expectedExecs = expected.getAuthenticationExecutions(); + List actualExecs = actual.getAuthenticationExecutions(); + + if (expectedExecs == null) { + Assert.assertTrue("Executions should be null or empty", actualExecs == null || actualExecs.size() == 0); + } else { + compareExecutions(expectedExecs, actualExecs); + } + } AuthenticationFlowRepresentation newFlow(String alias, String description, String providerId, boolean topLevel, boolean builtIn) { @@ -112,8 +140,8 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest { return flow; } - AuthenticationExecutionInfoRepresentation newExecution(String displayName, String providerId, Boolean configurable, - int level, int index, String requirement, Boolean authFlow, String[] choices) { + AuthenticationExecutionInfoRepresentation newExecInfo(String displayName, String providerId, Boolean configurable, + int level, int index, String requirement, Boolean authFlow, String[] choices) { AuthenticationExecutionInfoRepresentation execution = new AuthenticationExecutionInfoRepresentation(); execution.setRequirement(requirement); @@ -129,6 +157,12 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest { return execution; } + void addExecInfo(List target, String displayName, String providerId, Boolean configurable, + int level, int index, String requirement, Boolean authFlow, String[] choices) { + + AuthenticationExecutionInfoRepresentation exec = newExecInfo(displayName, providerId, configurable, level, index, requirement, authFlow, choices); + target.add(exec); + } AuthenticatorConfigRepresentation newConfig(String alias, String[] keyvalues) { AuthenticatorConfigRepresentation config = new AuthenticatorConfigRepresentation(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java index f9c0bba714..afe702f033 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java @@ -83,7 +83,7 @@ public class ExecutionTest extends AbstractAuthenticationTest { response.close(); } - compareExecution(newExecution("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec); + compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec); // remove execution authMgmtResource.removeExecution(exec.getId()); @@ -143,7 +143,7 @@ public class ExecutionTest extends AbstractAuthenticationTest { // Note: there is no checking in addExecution if requirement is one of requirementChoices // Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED - compareExecution(newExecution("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec); + compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec); } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java index 63c262ab0e..3d4a30794b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java @@ -19,12 +19,14 @@ package org.keycloak.testsuite.admin.authentication; import org.junit.Assert; import org.junit.Test; +import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import javax.ws.rs.BadRequestException; import javax.ws.rs.core.Response; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Marko Strukelj @@ -62,13 +64,52 @@ public class FlowTest extends AbstractAuthenticationTest { response.close(); } - // check that new flow is returned + // check that new flow is returned in a children list flows = authMgmtResource.getFlows(); AuthenticationFlowRepresentation found = findFlowByAlias("browser-2", flows); - Assert.assertNotNull("created flow visible", found); + Assert.assertNotNull("created flow visible in parent", found); compareFlows(newFlow, found); + // check that new flow is returned individually + AuthenticationFlowRepresentation found2 = authMgmtResource.getFlow(found.getId()); + Assert.assertNotNull("created flow visible directly", found2); + compareFlows(newFlow, found2); + + + // add execution flow using a different method + Map data = new HashMap<>(); + data.put("alias", "SomeFlow"); + data.put("type", "basic-flow"); + data.put("description", "Test flow"); + data.put("provider", "registration-page-form"); + + try { + authMgmtResource.addExecutionFlow("inexistent-parent-flow-alias", data); + Assert.fail("addExecutionFlow for inexistent parent should have failed"); + } catch (Exception expected) { + } + + authMgmtResource.addExecutionFlow("browser-2", data); + + // check that new flow is returned in a children list + flows = authMgmtResource.getFlows(); + found2 = findFlowByAlias("browser-2", flows); + Assert.assertNotNull("created flow visible in parent", found2); + + List execs = found2.getAuthenticationExecutions(); + Assert.assertNotNull(execs); + Assert.assertEquals("Size one", 1, execs.size()); + + AuthenticationExecutionExportRepresentation expected = new AuthenticationExecutionExportRepresentation(); + expected.setFlowAlias("SomeFlow"); + expected.setUserSetupAllowed(false); + expected.setAuthenticator("registration-page-form"); + expected.setAutheticatorFlow(true); + expected.setRequirement("DISABLED"); + expected.setPriority(0); + compareExecution(expected, execs.get(0)); + // delete non-built-in flow authMgmtResource.deleteFlow(found.getId()); @@ -122,6 +163,12 @@ public class FlowTest extends AbstractAuthenticationTest { // adjust expected values before comparing browser.setAlias("Copy of browser"); browser.setBuiltIn(false); + browser.getAuthenticationExecutions().get(2).setFlowAlias("Copy of browser forms"); + compareFlows(browser, copyOfBrowser); + + // get new flow directly and compare + copyOfBrowser = authMgmtResource.getFlow(copyOfBrowser.getId()); + Assert.assertNotNull(copyOfBrowser); compareFlows(browser, copyOfBrowser); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java index 1f42069adf..45f538fbd3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java @@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin.authentication; import org.junit.Assert; import org.junit.Test; +import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; @@ -81,12 +82,12 @@ public class InitialFlowsTest extends AbstractAuthenticationTest { FlowExecutions fe2 = it2.next(); compareFlows(fe1.flow, fe2.flow); - compareExecutions(fe1.executions, fe2.executions); + compareExecutionsInfo(fe1.executions, fe2.executions); } } - private void compareExecutions(List expected, List actual) { + private void compareExecutionsInfo(List expected, List actual) { Assert.assertEquals("Executions count", expected.size(), actual.size()); Iterator it1 = expected.iterator(); Iterator it2 = actual.iterator(); @@ -124,66 +125,117 @@ public class InitialFlowsTest extends AbstractAuthenticationTest { LinkedList expected = new LinkedList<>(); AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true); - List executions = new LinkedList<>(); - executions.add(newExecution("Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED})); - executions.add(newExecution("Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED})); - executions.add(newExecution("forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED})); - executions.add(newExecution("Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED})); - executions.add(newExecution("OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED})); - expected.add(new FlowExecutions(flow, executions)); + addExecExport(flow, null, false, "auth-cookie", false, null, ALTERNATIVE, 10); + addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 20); + addExecExport(flow, "forms", false, null, true, null, ALTERNATIVE, 30); + + List execs = new LinkedList<>(); + addExecInfo(execs, "Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}); + addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); + addExecInfo(execs, "forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); + addExecInfo(execs, "Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED}); + addExecInfo(execs, "OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}); + expected.add(new FlowExecutions(flow, execs)); flow = newFlow("clients", "Base authentication for clients", "client-flow", true, true); - executions = new LinkedList<>(); - executions.add(newExecution("Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED})); - executions.add(newExecution("Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED})); - expected.add(new FlowExecutions(flow, executions)); + addExecExport(flow, null, false, "client-secret", false, null, ALTERNATIVE, 10); + addExecExport(flow, null, false, "client-jwt", false, null, ALTERNATIVE, 20); + + execs = new LinkedList<>(); + addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}); + addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}); + expected.add(new FlowExecutions(flow, execs)); flow = newFlow("direct grant", "OpenID Connect Resource Owner Grant", "basic-flow", true, true); - executions = new LinkedList<>(); - executions.add(newExecution("Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED})); - executions.add(newExecution("Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED})); - executions.add(newExecution("OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED})); - expected.add(new FlowExecutions(flow, executions)); + addExecExport(flow, null, false, "direct-grant-validate-username", false, null, REQUIRED, 10); + addExecExport(flow, null, false, "direct-grant-validate-password", false, null, REQUIRED, 20); + addExecExport(flow, null, false, "direct-grant-validate-otp", false, null, OPTIONAL, 30); + + execs = new LinkedList<>(); + addExecInfo(execs, "Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}); + addExecInfo(execs, "Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}); + addExecInfo(execs, "OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}); + expected.add(new FlowExecutions(flow, execs)); flow = newFlow("first broker login", "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "basic-flow", true, true); - executions = new LinkedList<>(); - executions.add(newExecution("Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED})); - executions.add(newExecution("Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED})); - executions.add(newExecution("Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED})); - executions.add(newExecution("Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED})); - executions.add(newExecution("Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED})); - executions.add(newExecution("Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED})); - executions.add(newExecution("Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED})); - executions.add(newExecution("OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED})); - expected.add(new FlowExecutions(flow, executions)); + addExecExport(flow, null, false, "idp-review-profile", false, "review profile config", REQUIRED, 10); + addExecExport(flow, null, false, "idp-create-user-if-unique", false, "create unique user config", ALTERNATIVE, 20); + addExecExport(flow, "Handle Existing Account", false, null, true, null, ALTERNATIVE, 30); + + execs = new LinkedList<>(); + addExecInfo(execs, "Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}); + addExecInfo(execs, "Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); + addExecInfo(execs, "Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); + addExecInfo(execs, "Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}); + addExecInfo(execs, "Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); + addExecInfo(execs, "Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); + addExecInfo(execs, "Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED}); + addExecInfo(execs, "OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}); + expected.add(new FlowExecutions(flow, execs)); flow = newFlow("registration", "registration flow", "basic-flow", true, true); - executions = new LinkedList<>(); - executions.add(newExecution("registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED})); - executions.add(newExecution("Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED})); - executions.add(newExecution("Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED})); - executions.add(newExecution("Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED})); - executions.add(newExecution("Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED})); - expected.add(new FlowExecutions(flow, executions)); + addExecExport(flow, "registration form", false, "registration-page-form", true, null, REQUIRED, 10); + + execs = new LinkedList<>(); + addExecInfo(execs, "registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED}); + addExecInfo(execs, "Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}); + addExecInfo(execs, "Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}); + addExecInfo(execs, "Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED}); + addExecInfo(execs, "Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}); + expected.add(new FlowExecutions(flow, execs)); flow = newFlow("reset credentials", "Reset credentials for a user if they forgot their password or something", "basic-flow", true, true); - executions = new LinkedList<>(); - executions.add(newExecution("Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED})); - executions.add(newExecution("Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED})); - executions.add(newExecution("Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED})); - executions.add(newExecution("Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED})); - expected.add(new FlowExecutions(flow, executions)); + addExecExport(flow, null, false, "reset-credentials-choose-user", false, null, REQUIRED, 10); + addExecExport(flow, null, false, "reset-credential-email", false, null, REQUIRED, 20); + addExecExport(flow, null, false, "reset-password", false, null, REQUIRED, 30); + addExecExport(flow, null, false, "reset-otp", false, null, OPTIONAL, 40); + + execs = new LinkedList<>(); + addExecInfo(execs, "Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}); + addExecInfo(execs, "Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED}); + addExecInfo(execs, "Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED}); + addExecInfo(execs, "Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}); + expected.add(new FlowExecutions(flow, execs)); flow = newFlow("saml ecp", "SAML ECP Profile Authentication Flow", "basic-flow", true, true); - executions = new LinkedList<>(); - executions.add(newExecution(null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{})); - expected.add(new FlowExecutions(flow, executions)); + addExecExport(flow, null, false, "http-basic-authenticator", false, null, REQUIRED, 10); + + execs = new LinkedList<>(); + addExecInfo(execs, null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{}); + expected.add(new FlowExecutions(flow, execs)); return expected; } - static class FlowExecutions implements Comparable { + private void addExecExport(AuthenticationFlowRepresentation flow, String flowAlias, boolean userSetupAllowed, + String authenticator, boolean authenticatorFlow, String authenticatorConfig, + String requirement, int priority) { + + AuthenticationExecutionExportRepresentation rep = newExecutionExportRepresentation(flowAlias, userSetupAllowed, + authenticator, authenticatorFlow, authenticatorConfig, requirement, priority); + + List execs = flow.getAuthenticationExecutions(); + if (execs == null) { + execs = new ArrayList<>(); + flow.setAuthenticationExecutions(execs); + } + execs.add(rep); + } + + private AuthenticationExecutionExportRepresentation newExecutionExportRepresentation(String flowAlias, boolean userSetupAllowed, String authenticator, boolean authenticatorFlow, String authenticatorConfig, String requirement, int priority) { + AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation(); + rep.setFlowAlias(flowAlias); + rep.setUserSetupAllowed(userSetupAllowed); + rep.setAuthenticator(authenticator); + rep.setAutheticatorFlow(authenticatorFlow); + rep.setAuthenticatorConfig(authenticatorConfig); + rep.setRequirement(requirement); + rep.setPriority(priority); + return rep; + } + + private static class FlowExecutions implements Comparable { AuthenticationFlowRepresentation flow; List executions; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialProvidersTest.java deleted file mode 100644 index 2291bb9a80..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialProvidersTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.admin.authentication; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * @author Marko Strukelj - */ -public class InitialProvidersTest extends AbstractAuthenticationTest { - - @Test - public void testAuthenticationProvidersList() { - - List> providers = authMgmtResource.getAuthenticatorProviders(); - providers = sortProviders(providers); - - compareProviders(expectedAuthProviders(), providers); - } - - private void compareProviders(List> expected, List> actual) { - - Assert.assertEquals("Providers count", expected.size(), actual.size()); - - Iterator> it1 = expected.iterator(); - Iterator> it2 = actual.iterator(); - - while (it1.hasNext()) { - Assert.assertEquals("Provider", it1.next(), it2.next()); - } - } - - private List> expectedAuthProviders() { - ArrayList> result = new ArrayList<>(); - result.add(newClientProvider("auth-conditional-otp-form", "Conditional OTP Form", "Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions.")); - result.add(newClientProvider("auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server.")); - result.add(newClientProvider("auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form.")); - result.add(newClientProvider("auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos.")); - result.add(newClientProvider("auth-username-password-form", "Username Password Form", "Validates a username and password from login form.")); - result.add(newClientProvider("direct-grant-validate-otp", "OTP", "Validates the one time password supplied as a 'totp' form parameter in direct grant request")); - result.add(newClientProvider("direct-grant-validate-password", "Password", "Validates the password supplied as a 'password' form parameter in direct grant request")); - result.add(newClientProvider("direct-grant-validate-username", "Username Validation", "Validates the username supplied as a 'username' form parameter in direct grant request")); - result.add(newClientProvider("http-basic-authenticator", null, null)); - result.add(newClientProvider("idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict")); - result.add(newClientProvider("idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account with same email like identity provider. If no, create new user")); - result.add(newClientProvider("idp-email-verification", "Verify existing account by Email", "Email verification of existing Keycloak user, that wants to link his user account with identity provider")); - result.add(newClientProvider("idp-review-profile", "Review Profile", "User reviews and updates profile data retrieved from Identity Provider in the displayed form")); - result.add(newClientProvider("idp-username-password-form", "Username Password Form for identity provider reauthentication", "Validates a password from login form. Username is already known from identity provider authentication")); - result.add(newClientProvider("reset-credential-email", "Send Reset Email", "Send email to user and wait for response.")); - result.add(newClientProvider("reset-credentials-choose-user", "Choose User", "Choose a user to reset credentials for")); - result.add(newClientProvider("reset-otp", "Reset OTP", "Sets the Configure OTP required action if execution is REQUIRED. Will also set it if execution is OPTIONAL and the OTP is currently configured for it.")); - result.add(newClientProvider("reset-password", "Reset Password", "Sets the Update Password required action if execution is REQUIRED. Will also set it if execution is OPTIONAL and the password is currently configured for it.")); - return result; - } - - private Map newClientProvider(String id, String displayName, String description) { - Map obj = new HashMap<>(); - obj.put("id", id); - obj.put("displayName", displayName); - obj.put("description", description); - return obj; - } - - private List> sortProviders(List> providers) { - ArrayList> sorted = new ArrayList<>(providers); - Collections.sort(sorted, new ProviderComparator()); - return sorted; - } - - private static class ProviderComparator implements Comparator> { - @Override - public int compare(Map o1, Map o2) { - return String.valueOf(o1.get("id")).compareTo(String.valueOf(o2.get("id"))); - } - } - -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java new file mode 100644 index 0000000000..9f679e35c2 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java @@ -0,0 +1,159 @@ +/* + * 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.admin.authentication; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Marko Strukelj + */ +public class ProvidersTest extends AbstractAuthenticationTest { + + @Test + public void testFormProviders() { + List> result = authMgmtResource.getFormProviders(); + + Assert.assertNotNull("null result", result); + Assert.assertEquals("size", 1, result.size()); + Map item = result.get(0); + + Assert.assertEquals("id", "registration-page-form", item.get("id")); + Assert.assertEquals("displayName", "Registration Page", item.get("displayName")); + Assert.assertEquals("description", "This is the controller for the registration page", item.get("description")); + } + + @Test + public void testFormActionProviders() { + List> result = authMgmtResource.getFormActionProviders(); + + List> expected = new LinkedList<>(); + addProviderInfo(expected, "registration-profile-action", "Profile Validation", + "Validates email, first name, and last name attributes and stores them in user data."); + addProviderInfo(expected, "registration-recaptcha-action", "Recaptcha", + "Adds Google Recaptcha button. Recaptchas verify that the entity that is registering is a human. " + + "This can only be used on the internet and must be configured after you add it."); + addProviderInfo(expected, "registration-password-action", "Password Validation", + "Validates that password matches password confirmation field. It also will store password in user's credential store."); + addProviderInfo(expected, "registration-user-creation", "Registration User Creation", + "This action must always be first! Validates the username of the user in validation phase. " + + "In success phase, this will create the user in the database."); + + compareProviders(expected, result); + } + + @Test + public void testClientAuthenticatorProviders() { + List> result = authMgmtResource.getClientAuthenticatorProviders(); + + List> expected = new LinkedList<>(); + addProviderInfo(expected, "client-jwt", "Signed Jwt", + "Validates client based on signed JWT issued by client and signed with the Client private key"); + addProviderInfo(expected, "client-secret", "Client Id and Secret", "Validates client based on 'client_id' and " + + "'client_secret' sent either in request parameters or in 'Authorization: Basic' header"); + + compareProviders(expected, result); + } + + + @Test + public void testInitialAuthenticationProviders() { + + List> providers = authMgmtResource.getAuthenticatorProviders(); + providers = sortProviders(providers); + + compareProviders(expectedAuthProviders(), providers); + } + + private List> expectedAuthProviders() { + ArrayList> result = new ArrayList<>(); + addProviderInfo(result, "auth-conditional-otp-form", "Conditional OTP Form", + "Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions."); + addProviderInfo(result, "auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server."); + addProviderInfo(result, "auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form."); + addProviderInfo(result, "auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos."); + addProviderInfo(result, "auth-username-password-form", "Username Password Form", + "Validates a username and password from login form."); + addProviderInfo(result, "direct-grant-validate-otp", "OTP", "Validates the one time password supplied as a 'totp' form parameter in direct grant request"); + addProviderInfo(result, "direct-grant-validate-password", "Password", + "Validates the password supplied as a 'password' form parameter in direct grant request"); + addProviderInfo(result, "direct-grant-validate-username", "Username Validation", + "Validates the username supplied as a 'username' form parameter in direct grant request"); + addProviderInfo(result, "http-basic-authenticator", null, null); + addProviderInfo(result, "idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants " + + "to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict"); + addProviderInfo(result, "idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account " + + "with same email like identity provider. If no, create new user"); + addProviderInfo(result, "idp-email-verification", "Verify existing account by Email", "Email verification of existing Keycloak " + + "user, that wants to link his user account with identity provider"); + addProviderInfo(result, "idp-review-profile", "Review Profile", + "User reviews and updates profile data retrieved from Identity Provider in the displayed form"); + addProviderInfo(result, "idp-username-password-form", "Username Password Form for identity provider reauthentication", + "Validates a password from login form. Username is already known from identity provider authentication"); + addProviderInfo(result, "reset-credential-email", "Send Reset Email", "Send email to user and wait for response."); + addProviderInfo(result, "reset-credentials-choose-user", "Choose User", "Choose a user to reset credentials for"); + addProviderInfo(result, "reset-otp", "Reset OTP", "Sets the Configure OTP required action if execution is REQUIRED. " + + "Will also set it if execution is OPTIONAL and the OTP is currently configured for it."); + addProviderInfo(result, "reset-password", "Reset Password", "Sets the Update Password required action if execution is REQUIRED. " + + "Will also set it if execution is OPTIONAL and the password is currently configured for it."); + return result; + } + + private List> sortProviders(List> providers) { + ArrayList> sorted = new ArrayList<>(providers); + Collections.sort(sorted, new ProviderComparator()); + return sorted; + } + + private void compareProviders(List> expected, List> actual) { + Assert.assertEquals("Providers count", expected.size(), actual.size()); + // compare ignoring list and map impl types + Assert.assertEquals(normalizeResults(expected), normalizeResults(actual)); + } + + private List> normalizeResults(List> list) { + ArrayList> result = new ArrayList(); + for (Map item: list) { + result.add(new HashMap(item)); + } + return result; + } + + private void addProviderInfo(List> list, String id, String displayName, String description) { + HashMap item = new HashMap<>(); + item.put("id", id); + item.put("displayName", displayName); + item.put("description", description); + list.add(item); + } + + private static class ProviderComparator implements Comparator> { + @Override + public int compare(Map o1, Map o2) { + return String.valueOf(o1.get("id")).compareTo(String.valueOf(o2.get("id"))); + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java new file mode 100644 index 0000000000..0ac68649e4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java @@ -0,0 +1,121 @@ +/* + * 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.admin.authentication; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.representations.idm.RequiredActionProviderRepresentation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * @author Marko Strukelj + */ +public class RequiredActionsTest extends AbstractAuthenticationTest { + + @Test + public void testRequiredActions() { + List result = authMgmtResource.getRequiredActions(); + + List expected = new ArrayList<>(); + addRequiredAction(expected, "CONFIGURE_TOTP", "Configure Totp", true, false, null); + addRequiredAction(expected, "UPDATE_PASSWORD", "Update Password", true, false, null); + addRequiredAction(expected, "UPDATE_PROFILE", "Update Profile", true, false, null); + addRequiredAction(expected, "VERIFY_EMAIL", "Verify Email", true, false, null); + addRequiredAction(expected, "terms_and_conditions", "Terms and Conditions", false, false, null); + + compareRequiredActions(expected, sort(result)); + + RequiredActionProviderRepresentation forUpdate = newRequiredAction("VERIFY_EMAIL", "Verify Email", false, false, null); + try { + authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate); + Assert.fail("updateRequiredAction should fail due to null config"); + } catch (Exception ignored) { + } + + forUpdate.setConfig(Collections.emptyMap()); + authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate); + + result = authMgmtResource.getRequiredActions(); + RequiredActionProviderRepresentation updated = findRequiredActionByAlias(forUpdate.getAlias(), result); + + Assert.assertNotNull("Required Action still there", updated); + compareRequiredAction(forUpdate, updated); + } + + + private RequiredActionProviderRepresentation findRequiredActionByAlias(String alias, List list) { + for (RequiredActionProviderRepresentation a: list) { + if (alias.equals(a.getAlias())) { + return a; + } + } + return null; + } + + private List sort(List list) { + ArrayList sorted = new ArrayList<>(list); + Collections.sort(sorted, new RequiredActionProviderComparator()); + return sorted; + } + + private void compareRequiredActions(List expected, List actual) { + Assert.assertNotNull("Actual null", actual); + Assert.assertEquals("Required actions count", expected.size(), actual.size()); + + Iterator ite = expected.iterator(); + Iterator ita = actual.iterator(); + while (ite.hasNext()) { + compareRequiredAction(ite.next(), ita.next()); + } + } + + private void compareRequiredAction(RequiredActionProviderRepresentation expected, RequiredActionProviderRepresentation actual) { + Assert.assertEquals("alias - " + expected.getAlias(), expected.getAlias(), actual.getAlias()); + Assert.assertEquals("name - " + expected.getAlias(), expected.getName(), actual.getName()); + Assert.assertEquals("enabled - " + expected.getAlias(), expected.isEnabled(), actual.isEnabled()); + Assert.assertEquals("defaultAction - " + expected.getAlias(), expected.isDefaultAction(), actual.isDefaultAction()); + Assert.assertEquals("config - " + expected.getAlias(), expected.getConfig() != null ? expected.getConfig() : Collections.emptyMap(), actual.getConfig()); + } + + private void addRequiredAction(List target, String alias, String name, boolean enabled, boolean defaultAction, Map conf) { + target.add(newRequiredAction(alias, name, enabled, defaultAction, conf)); + } + + private RequiredActionProviderRepresentation newRequiredAction(String alias, String name, boolean enabled, boolean defaultAction, Map conf) { + RequiredActionProviderRepresentation action = new RequiredActionProviderRepresentation(); + action.setAlias(alias); + action.setName(name); + action.setEnabled(enabled); + action.setDefaultAction(defaultAction); + action.setConfig(conf); + return action; + } + + private static class RequiredActionProviderComparator implements Comparator { + @Override + public int compare(RequiredActionProviderRepresentation o1, RequiredActionProviderRepresentation o2) { + return o1.getAlias().compareTo(o2.getAlias()); + } + } +}