Merge pull request #2314 from mstruk/admin-client

KEYCLOAK-2574 Add more admin-client authentication tests
This commit is contained in:
Stian Thorgersen 2016-03-03 06:30:48 +01:00
commit e4815cd103
7 changed files with 465 additions and 153 deletions

View file

@ -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();

View file

@ -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

View file

@ -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);
}

View file

@ -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;

View file

@ -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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class InitialProvidersTest extends AbstractAuthenticationTest {
@Test
public void testAuthenticationProvidersList() {
List<Map<String, Object>> providers = authMgmtResource.getAuthenticatorProviders();
providers = sortProviders(providers);
compareProviders(expectedAuthProviders(), providers);
}
private void compareProviders(List<Map<String, Object>> expected, List<Map<String, Object>> actual) {
Assert.assertEquals("Providers count", expected.size(), actual.size());
Iterator<Map<String, Object>> it1 = expected.iterator();
Iterator<Map<String, Object>> it2 = actual.iterator();
while (it1.hasNext()) {
Assert.assertEquals("Provider", it1.next(), it2.next());
}
}
private List<Map<String, Object>> expectedAuthProviders() {
ArrayList<Map<String, Object>> 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<String, Object> newClientProvider(String id, String displayName, String description) {
Map<String, Object> obj = new HashMap<>();
obj.put("id", id);
obj.put("displayName", displayName);
obj.put("description", description);
return obj;
}
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 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")));
}
}
}

View file

@ -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")));
}
}
}

View file

@ -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());
}
}
}