/*
* 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.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class InitialFlowsTest extends AbstractAuthenticationTest {
private HashMap<String, AuthenticatorConfigRepresentation> configs = new HashMap<>();
private HashMap<String, AuthenticatorConfigRepresentation> expectedConfigs = new HashMap<>();
{
expectedConfigs.put("idp-review-profile", newConfig("review profile config", new String[]{"update.profile.on.first.login", "missing"}));
expectedConfigs.put("idp-create-user-if-unique", newConfig("create unique user config", new String[]{"require.password.update.after.registration", "false"}));
}
@Test
public void testInitialFlows() {
List<FlowExecutions> result = new LinkedList<>();
// get all flows
List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
for (AuthenticationFlowRepresentation flow : flows) {
// get all executions for flow
List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions(flow.getAlias());
for (AuthenticationExecutionInfoRepresentation exec : executionReps) {
// separately load referenced configurations
String configId = exec.getAuthenticationConfig();
if (configId != null && !configs.containsKey(configId)) {
configs.put(configId, authMgmtResource.getAuthenticatorConfig(configId));
}
}
result.add(new FlowExecutions(flow, executionReps));
}
// make sure received flows and their details are as expected
compare(expectedFlows(), orderAlphabetically(result));
}
private void compare(List<FlowExecutions> expected, List<FlowExecutions> actual) {
Assert.assertEquals("Flow count", expected.size(), actual.size());
Iterator<FlowExecutions> it1 = expected.iterator();
Iterator<FlowExecutions> it2 = actual.iterator();
while (it1.hasNext()) {
FlowExecutions fe1 = it1.next();
FlowExecutions fe2 = it2.next();
compareFlows(fe1.flow, fe2.flow);
compareExecutionsInfo(fe1.executions, fe2.executions);
}
}
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();
while (it1.hasNext()) {
AuthenticationExecutionInfoRepresentation exe1 = it1.next();
AuthenticationExecutionInfoRepresentation exe2 = it2.next();
compareExecutionWithConfig(exe1, exe2);
}
}
private void compareExecutionWithConfig(AuthenticationExecutionInfoRepresentation expected, AuthenticationExecutionInfoRepresentation actual) {
super.compareExecution(expected, actual);
compareAuthConfig(expected, actual);
}
private void compareAuthConfig(AuthenticationExecutionInfoRepresentation expected, AuthenticationExecutionInfoRepresentation actual) {
AuthenticatorConfigRepresentation cfg1 = expectedConfigs.get(expected.getProviderId());
AuthenticatorConfigRepresentation cfg2 = configs.get(actual.getAuthenticationConfig());
if (cfg1 == null && cfg2 == null) {
return;
}
Assert.assertEquals("Execution configuration alias", cfg1.getAlias(), cfg2.getAlias());
Assert.assertEquals("Execution configuration params", cfg1.getConfig(), cfg2.getConfig());
}
private List<FlowExecutions> orderAlphabetically(List<FlowExecutions> result) {
List<FlowExecutions> sorted = new ArrayList<>(result);
Collections.sort(sorted);
return sorted;
}
private LinkedList<FlowExecutions> expectedFlows() {
LinkedList<FlowExecutions> expected = new LinkedList<>();
AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true);
addExecExport(flow, null, false, "auth-cookie", false, null, ALTERNATIVE, 10);
addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 20);
addExecExport(flow, null, false, "identity-provider-redirector", false, null, ALTERNATIVE, 25);
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, "Identity Provider Redirector", "identity-provider-redirector", true, 0, 2, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
addExecInfo(execs, "forms", null, false, 0, 3, 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);
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);
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);
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);
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);
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));
return expected;
}
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;
FlowExecutions(AuthenticationFlowRepresentation flow, List<AuthenticationExecutionInfoRepresentation> executions) {
this.flow = flow;
this.executions = executions;
}
@Override
public int compareTo(FlowExecutions o) {
return flow.getAlias().compareTo(o.flow.getAlias());
}
}
}