keycloak-aplcache

Details

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 70013c5..fb7e966 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<AuthenticationExecutionExportRepresentation> expected, List<AuthenticationExecutionExportRepresentation> 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<AuthenticationExecutionExportRepresentation> expectedExecs = expected.getAuthenticationExecutions();
+        List<AuthenticationExecutionExportRepresentation> 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<AuthenticationExecutionInfoRepresentation> 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 f9c0bba..afe702f 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 63c262a..3d4a307 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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@@ -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<String, String> 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<AuthenticationExecutionExportRepresentation> 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 1f42069..45f538f 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<AuthenticationExecutionInfoRepresentation> expected, List<AuthenticationExecutionInfoRepresentation> actual) {
+    private void compareExecutionsInfo(List<AuthenticationExecutionInfoRepresentation> expected, List<AuthenticationExecutionInfoRepresentation> actual) {
         Assert.assertEquals("Executions count", expected.size(), actual.size());
         Iterator<AuthenticationExecutionInfoRepresentation> it1 = expected.iterator();
         Iterator<AuthenticationExecutionInfoRepresentation> it2 = actual.iterator();
@@ -124,66 +125,117 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
         LinkedList<FlowExecutions> expected = new LinkedList<>();
 
         AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true);
-        List<AuthenticationExecutionInfoRepresentation> 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<AuthenticationExecutionInfoRepresentation> 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<FlowExecutions> {
+    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<AuthenticationExecutionExportRepresentation> 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<FlowExecutions> {
         AuthenticationFlowRepresentation flow;
         List<AuthenticationExecutionInfoRepresentation> executions;
 
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 0000000..9f679e3
--- /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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ProvidersTest extends AbstractAuthenticationTest {
+
+    @Test
+    public void testFormProviders() {
+        List<Map<String, Object>> result = authMgmtResource.getFormProviders();
+
+        Assert.assertNotNull("null result", result);
+        Assert.assertEquals("size", 1, result.size());
+        Map<String, Object> 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<Map<String, Object>> result = authMgmtResource.getFormActionProviders();
+
+        List<Map<String, Object>> 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<Map<String, Object>> result = authMgmtResource.getClientAuthenticatorProviders();
+
+        List<Map<String, Object>> 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<Map<String, Object>> providers = authMgmtResource.getAuthenticatorProviders();
+        providers = sortProviders(providers);
+
+        compareProviders(expectedAuthProviders(), providers);
+    }
+
+    private List<Map<String, Object>> expectedAuthProviders() {
+        ArrayList<Map<String, Object>> 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<Map<String, Object>> sortProviders(List<Map<String, Object>> providers) {
+        ArrayList<Map<String, Object>> sorted = new ArrayList<>(providers);
+        Collections.sort(sorted, new ProviderComparator());
+        return sorted;
+    }
+
+    private void compareProviders(List<Map<String, Object>> expected, List<Map<String, Object>> actual) {
+        Assert.assertEquals("Providers count", expected.size(), actual.size());
+        // compare ignoring list and map impl types
+        Assert.assertEquals(normalizeResults(expected), normalizeResults(actual));
+    }
+
+    private List<Map<String, Object>> normalizeResults(List<Map<String, Object>> list) {
+        ArrayList<Map<String, Object>> result = new ArrayList();
+        for (Map<String, Object> item: list) {
+            result.add(new HashMap(item));
+        }
+        return result;
+    }
+
+    private void addProviderInfo(List<Map<String, Object>> list, String id, String displayName, String description) {
+        HashMap<String, Object> item = new HashMap<>();
+        item.put("id", id);
+        item.put("displayName", displayName);
+        item.put("description", description);
+        list.add(item);
+    }
+
+    private static class ProviderComparator implements Comparator<Map<String, Object>> {
+        @Override
+        public int compare(Map<String, Object> o1, Map<String, Object> 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 0000000..0ac6864
--- /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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class RequiredActionsTest extends AbstractAuthenticationTest {
+
+    @Test
+    public void testRequiredActions() {
+        List<RequiredActionProviderRepresentation> result = authMgmtResource.getRequiredActions();
+
+        List<RequiredActionProviderRepresentation> 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.<String, String>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<RequiredActionProviderRepresentation> list) {
+        for (RequiredActionProviderRepresentation a: list) {
+            if (alias.equals(a.getAlias())) {
+                return a;
+            }
+        }
+        return null;
+    }
+
+    private List<RequiredActionProviderRepresentation> sort(List<RequiredActionProviderRepresentation> list) {
+        ArrayList<RequiredActionProviderRepresentation> sorted = new ArrayList<>(list);
+        Collections.sort(sorted, new RequiredActionProviderComparator());
+        return sorted;
+    }
+
+    private void compareRequiredActions(List<RequiredActionProviderRepresentation> expected, List<RequiredActionProviderRepresentation> actual) {
+        Assert.assertNotNull("Actual null", actual);
+        Assert.assertEquals("Required actions count", expected.size(), actual.size());
+
+        Iterator<RequiredActionProviderRepresentation> ite = expected.iterator();
+        Iterator<RequiredActionProviderRepresentation> 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<RequiredActionProviderRepresentation> 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<RequiredActionProviderRepresentation> {
+        @Override
+        public int compare(RequiredActionProviderRepresentation o1, RequiredActionProviderRepresentation o2) {
+            return o1.getAlias().compareTo(o2.getAlias());
+        }
+    }
+}