Allow setting authentication flow execution priority value via Admin API

Closes #20747

Signed-off-by: Andrejs Mivreniks <andrejs@fastmail.com>
This commit is contained in:
Andrejs Mivreniks 2024-05-29 19:42:03 +03:00 committed by Marek Posolda
parent 3f49036192
commit 1cf87407fe
20 changed files with 216 additions and 98 deletions

View file

@ -28,7 +28,7 @@ public class AbstractAuthenticationExecutionRepresentation implements Serializab
private String authenticator;
private boolean authenticatorFlow;
private String requirement;
private int priority;
private Integer priority;
public String getAuthenticatorConfig() {
return authenticatorConfig;
@ -54,11 +54,11 @@ public class AbstractAuthenticationExecutionRepresentation implements Serializab
this.requirement = requirement;
}
public int getPriority() {
public Integer getPriority() {
return priority;
}
public void setPriority(int priority) {
public void setPriority(Integer priority) {
this.priority = priority;
}

View file

@ -39,6 +39,7 @@ public class AuthenticationExecutionInfoRepresentation implements Serializable {
protected String flowId;
protected int level;
protected int index;
protected int priority;
public String getId() {
return id;
@ -143,4 +144,12 @@ public class AuthenticationExecutionInfoRepresentation implements Serializable {
public void setFlowId(String flowId) {
this.flowId = flowId;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
}

View file

@ -88,7 +88,7 @@ public interface AuthenticationManagementResource {
@Path("/flows/{flowAlias}/copy")
@POST
@Consumes(MediaType.APPLICATION_JSON)
Response copy(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
Response copy(@PathParam("flowAlias") String flowAlias, Map<String, Object> data);
@Path("/flows/{id}")
@PUT
@ -98,12 +98,12 @@ public interface AuthenticationManagementResource {
@Path("/flows/{flowAlias}/executions/flow")
@POST
@Consumes(MediaType.APPLICATION_JSON)
void addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
void addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map<String, Object> data);
@Path("/flows/{flowAlias}/executions/execution")
@POST
@Consumes(MediaType.APPLICATION_JSON)
void addExecution(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
void addExecution(@PathParam("flowAlias") String flowAlias, Map<String, Object> data);
@Path("/flows/{flowAlias}/executions")
@GET

View file

@ -1416,7 +1416,9 @@ public class DefaultExportImportManager implements ExportImportManager {
AuthenticationFlowModel flow = realm.getFlowByAlias(rep.getFlowAlias());
model.setFlowId(flow.getId());
}
if (rep.getPriority() != null) {
model.setPriority(rep.getPriority());
}
try {
model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));
model.setParentFlow(parentFlow.getId());

View file

@ -984,7 +984,9 @@ public class RepresentationToModel {
model.setFlowId(rep.getFlowId());
model.setAuthenticator(rep.getAuthenticator());
if (rep.getPriority() != null) {
model.setPriority(rep.getPriority());
}
model.setParentFlow(rep.getParentFlow());
model.setAuthenticatorFlow(rep.isAuthenticatorFlow());
model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));

View file

@ -471,7 +471,7 @@ public class AuthenticationManagementResource {
* Add new flow with new execution to existing flow
*
* @param flowAlias Alias of parent authentication flow
* @param data New authentication flow / execution JSON data containing 'alias', 'type', 'provider', and 'description' attributes
* @param data New authentication flow / execution JSON data containing 'alias', 'type', 'provider', 'priority', and 'description' attributes
*/
@Path("/flows/{flowAlias}/executions/flow")
@POST
@ -479,7 +479,7 @@ public class AuthenticationManagementResource {
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = KeycloakOpenAPI.Admin.Tags.AUTHENTICATION_MANAGEMENT)
@Operation( summary = "Add new flow with new execution to existing flow")
public Response addExecutionFlow(@Parameter(description = "Alias of parent authentication flow") @PathParam("flowAlias") String flowAlias, @Parameter(description = "New authentication flow / execution JSON data containing 'alias', 'type', 'provider', and 'description' attributes") Map<String, String> data) {
public Response addExecutionFlow(@Parameter(description = "Alias of parent authentication flow") @PathParam("flowAlias") String flowAlias, @Parameter(description = "New authentication flow / execution JSON data containing 'alias', 'type', 'provider', 'priority', and 'description' attributes") Map<String, Object> data) {
auth.realm().requireManageRealm();
AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias);
@ -489,12 +489,13 @@ public class AuthenticationManagementResource {
if (parentFlow.isBuiltIn()) {
throw new BadRequestException("It is illegal to add sub-flow to a built in flow");
}
String alias = data.get("alias");
String type = data.get("type");
String provider = data.get("provider");
String alias = (String) data.get("alias");
String type = (String) data.get("type");
String provider = (String) data.get("provider");
int priority = data.containsKey("priority") ? (Integer) data.get("priority") : getNextPriority(parentFlow);
//Make sure that the description to avoid NullPointerException
String description = Objects.isNull(data.get("description")) ? "" : data.get("description");
String description = Objects.isNull(data.get("description")) ? "" : (String) data.get("description");
AuthenticationFlowModel newFlow = realm.getFlowByAlias(alias);
@ -514,7 +515,7 @@ public class AuthenticationManagementResource {
if (type.equals("form-flow")) {
execution.setAuthenticator(provider);
}
execution.setPriority(getNextPriority(parentFlow));
execution.setPriority(priority);
execution = realm.addAuthenticatorExecution(execution);
data.put("id", execution.getId());
@ -534,7 +535,7 @@ public class AuthenticationManagementResource {
* Add new authentication execution to a flow
*
* @param flowAlias Alias of parent flow
* @param data New execution JSON data containing 'provider' attribute
* @param data New execution JSON data containing 'provider' and 'priority' (optional) attribute
*/
@Path("/flows/{flowAlias}/executions/execution")
@POST
@ -542,7 +543,7 @@ public class AuthenticationManagementResource {
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = KeycloakOpenAPI.Admin.Tags.AUTHENTICATION_MANAGEMENT)
@Operation( summary="Add new authentication execution to a flow")
public Response addExecutionToFlow(@Parameter(description = "Alias of parent flow") @PathParam("flowAlias") String flowAlias, @Parameter(description = "New execution JSON data containing 'provider' attribute") Map<String, String> data) {
public Response addExecutionToFlow(@Parameter(description = "Alias of parent flow") @PathParam("flowAlias") String flowAlias, @Parameter(description = "New execution JSON data containing 'provider' and 'priority' (optional) attribute") Map<String, Object> data) {
auth.realm().requireManageRealm();
AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias);
@ -552,7 +553,8 @@ public class AuthenticationManagementResource {
if (parentFlow.isBuiltIn()) {
throw new BadRequestException("It is illegal to add execution to a built in flow");
}
String provider = data.get("provider");
String provider = (String) data.get("provider");
int priority = data.containsKey("priority") ? (Integer) data.get("priority") : getNextPriority(parentFlow);
// make sure provider is one of the registered providers
ProviderFactory f = getProviderFactory( parentFlow, provider);
@ -568,7 +570,7 @@ public class AuthenticationManagementResource {
execution.setAuthenticatorFlow(false);
execution.setAuthenticator(provider);
execution.setPriority(getNextPriority(parentFlow));
execution.setPriority(priority);
execution = realm.addAuthenticatorExecution(execution);
@ -647,6 +649,7 @@ public class AuthenticationManagementResource {
rep.setLevel(level);
rep.setIndex(index.getAndIncrement());
rep.setRequirementChoices(new LinkedList<>());
rep.setPriority(execution.getPriority());
if (execution.isAuthenticatorFlow()) {
AuthenticationFlowModel flowRef = realm.getAuthenticationFlowById(execution.getFlowId());
if (AuthenticationFlow.BASIC_FLOW.equals(flowRef.getProviderId())) {
@ -741,8 +744,16 @@ public class AuthenticationManagementResource {
throw new NotFoundException("Illegal execution");
}
boolean updateExecution = false;
if (model.getPriority() != rep.getPriority()) {
model.setPriority(rep.getPriority());
updateExecution = true;
}
if (!model.getRequirement().name().equals(rep.getRequirement())) {
model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));
updateExecution = true;
}
if (updateExecution) {
realm.updateAuthenticatorExecution(model);
adminEvent.operation(OperationType.UPDATE).resource(ResourceType.AUTH_EXECUTION).resourcePath(session.getContext().getUri()).representation(rep).success();
return Response.accepted(flow).build();
@ -825,7 +836,8 @@ public class AuthenticationManagementResource {
if (parentFlow.isBuiltIn()) {
throw new BadRequestException("It is illegal to add execution to a built in flow");
}
model.setPriority(getNextPriority(parentFlow));
int priority = execution.getPriority() != null ? execution.getPriority() : getNextPriority(parentFlow);
model.setPriority(priority);
model = realm.addAuthenticatorExecution(model);
if (!execution.isAuthenticatorFlow()) {

View file

@ -178,7 +178,7 @@ public class Creator<T> implements AutoCloseable {
}
public AuthenticationExecutionInfoRepresentation addExecution(String providerId) {
Map<String, String> c = new HashMap<>();
Map<String, Object> c = new HashMap<>();
c.put("provider", providerId);
resource().addExecution(alias, c); // addExecution only handles "provider" in data
return resource().getExecutions(alias).stream()

View file

@ -1054,7 +1054,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
}
private void configureBrowserFlowWithWebAuthnAuthenticator(String newFlowAlias) {
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("newName", newFlowAlias);
Response response = testRealm().flows().copy("browser", params);
response.close();

View file

@ -531,7 +531,7 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
response.close();
//add execution - username-password form
Map<String, String> data = new HashMap<>();
Map<String, Object> data = new HashMap<>();
data.put("provider", "auth-username-password-form");
getAuthMgmtResource().addExecution(flowAlias, data);

View file

@ -1137,17 +1137,17 @@ public class PermissionsTest extends AbstractKeycloakTest {
}, Resource.REALM, true);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
response.set(realm.flows().copy("nosuch", Collections.<String, String>emptyMap()));
response.set(realm.flows().copy("nosuch", Collections.<String, Object>emptyMap()));
}
}, Resource.REALM, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
realm.flows().addExecutionFlow("nosuch", Collections.<String, String>emptyMap());
realm.flows().addExecutionFlow("nosuch", Collections.<String, Object>emptyMap());
}
}, Resource.REALM, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
realm.flows().addExecution("nosuch", Collections.<String, String>emptyMap());
realm.flows().addExecution("nosuch", Collections.<String, Object>emptyMap());
}
}, Resource.REALM, true);
invoke(new Invocation() {

View file

@ -84,6 +84,25 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
return null;
}
/**
* Searches for an execution located before the provided execution on the same level of
* an authentication flow.
*
* @param execution execution to find a neighbor for
* @param executions list of executions to search in
* @return execution, or null if not found
*/
public static AuthenticationExecutionInfoRepresentation findPreviousExecution(AuthenticationExecutionInfoRepresentation execution, List<AuthenticationExecutionInfoRepresentation> executions) {
for (AuthenticationExecutionInfoRepresentation exec : executions) {
if (exec.getLevel() != execution.getLevel()) {
continue;
}
if (exec.getIndex() == execution.getIndex() - 1) {
return exec;
}
}
return null;
}
public static AuthenticationFlowRepresentation findFlowByAlias(String alias, List<AuthenticationFlowRepresentation> flows) {
for (AuthenticationFlowRepresentation flow : flows) {
@ -101,6 +120,7 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
Assert.assertEquals("Execution provider id - " + actual.getProviderId(), expected.getProviderId(), actual.getProviderId());
Assert.assertEquals("Execution level - " + actual.getProviderId(), expected.getLevel(), actual.getLevel());
Assert.assertEquals("Execution index - " + actual.getProviderId(), expected.getIndex(), actual.getIndex());
Assert.assertEquals("Execution priority - " + actual.getProviderId(), expected.getPriority(), actual.getPriority());
Assert.assertEquals("Execution authentication flow - " + actual.getProviderId(), expected.getAuthenticationFlow(), actual.getAuthenticationFlow());
Assert.assertEquals("Execution requirement choices - " + actual.getProviderId(), expected.getRequirementChoices(), actual.getRequirementChoices());
}
@ -153,7 +173,8 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
}
AuthenticationExecutionInfoRepresentation newExecInfo(String displayName, String providerId, Boolean configurable,
int level, int index, String requirement, Boolean authFlow, String[] choices) {
int level, int index, String requirement, Boolean authFlow, String[] choices,
int priority) {
AuthenticationExecutionInfoRepresentation execution = new AuthenticationExecutionInfoRepresentation();
execution.setRequirement(requirement);
@ -163,6 +184,7 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
execution.setLevel(level);
execution.setIndex(index);
execution.setAuthenticationFlow(authFlow);
execution.setPriority(priority);
if (choices != null) {
execution.setRequirementChoices(Arrays.asList(choices));
}
@ -170,9 +192,9 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
}
void addExecInfo(List<AuthenticationExecutionInfoRepresentation> target, String displayName, String providerId, Boolean configurable,
int level, int index, String requirement, Boolean authFlow, String[] choices) {
int level, int index, String requirement, Boolean authFlow, String[] choices, int priority) {
AuthenticationExecutionInfoRepresentation exec = newExecInfo(displayName, providerId, configurable, level, index, requirement, authFlow, choices);
AuthenticationExecutionInfoRepresentation exec = newExecInfo(displayName, providerId, configurable, level, index, requirement, authFlow, choices, priority);
target.add(exec);
}

View file

@ -49,7 +49,7 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest {
AuthenticationFlowRepresentation flowRep = newFlow("firstBrokerLogin2", "firstBrokerLogin2", "basic-flow", true, false);
createFlow(flowRep);
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("provider", IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID);
authMgmtResource.addExecution("firstBrokerLogin2", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("firstBrokerLogin2"), params, ResourceType.AUTH_EXECUTION);

View file

@ -52,7 +52,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
@Test
public void testUpdateAuthenticatorConfig() {
// copy built-in flow so we get a new editable flow
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "new-browser-flow");
Response response = authMgmtResource.copy("browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
@ -104,7 +104,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
public void testAddRemoveExecution() {
// try add execution to built-in flow
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("provider", "idp-review-profile");
try {
authMgmtResource.addExecution("browser", params);
@ -153,7 +153,9 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// we'll need auth-cookie later
AuthenticationExecutionInfoRepresentation authCookieExec = findExecutionByProvider("auth-cookie", executionReps);
compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 4, DISABLED, null, new String[]{REQUIRED, ALTERNATIVE,DISABLED}), exec);
AuthenticationExecutionInfoRepresentation previousExecution = findPreviousExecution(exec, executionReps);
Assert.assertNotNull(previousExecution);
compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 4, DISABLED, null, new String[]{REQUIRED, ALTERNATIVE,DISABLED}, previousExecution.getPriority() + 1), exec);
// remove execution
authMgmtResource.removeExecution(exec.getId());
@ -223,7 +225,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(newExecInfo("Cookie", "auth-cookie", false, 0, 3, CONDITIONAL, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}), exec);
compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 0, CONDITIONAL, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 10), exec);
}
@Test
@ -234,9 +236,11 @@ public class ExecutionTest extends AbstractAuthenticationTest {
AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("auth-cookie", executionReps);
Assert.assertEquals("auth-cookie set to ALTERNATIVE", ALTERNATIVE, exec.getRequirement());
Assert.assertEquals("auth-cookie is first in the flow", exec.getIndex(), 0);
// switch from DISABLED to ALTERNATIVE
exec.setRequirement(DISABLED);
exec.setPriority(Integer.MAX_VALUE);
authMgmtResource.updateExecutions("browser", exec);
assertAdminEvents.assertEvent(testRealmId, OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("browser"), exec, ResourceType.AUTH_EXECUTION);
@ -245,6 +249,13 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// get current auth-cookie execution
AuthenticationExecutionInfoRepresentation exec2 = findExecutionByProvider("auth-cookie", executionReps);
// The execution is expected to be last after priority change
long expectedIndex = executionReps.stream()
.filter(r -> r.getLevel() == exec2.getLevel())
.count() - 1;
exec.setIndex(Math.toIntExact(expectedIndex));
compareExecution(exec, exec2);
}
@ -255,7 +266,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
createFlow(clientFlow);
// Add execution to it
Map<String, String> executionData = new HashMap<>();
Map<String, Object> executionData = new HashMap<>();
executionData.put("provider", ClientIdAndSecretAuthenticator.PROVIDER_ID);
authMgmtResource.addExecution("new-client-flow", executionData);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("new-client-flow"), executionData, ResourceType.AUTH_EXECUTION);
@ -319,7 +330,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
@Test
public void testRequirementsInExecution() {
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
String newBrowserFlow = "new-exec-flow";
params.put("newName", newBrowserFlow);
@ -337,7 +348,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authFlowPath(rep.getId()), ResourceType.AUTH_FLOW);
}
private void addExecutionCheckReq(String flow, String providerID, HashMap<String, String> params, String expectedRequirement) {
private void addExecutionCheckReq(String flow, String providerID, HashMap<String, Object> params, String expectedRequirement) {
params.put("provider", providerID);
authMgmtResource.addExecution(flow, params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath(flow), params, ResourceType.AUTH_EXECUTION);

View file

@ -108,7 +108,7 @@ public class FlowTest extends AbstractAuthenticationTest {
}
private void addFlowToParent(String parentAlias, String childAlias) {
Map<String, String> data = new HashMap<>();
Map<String, Object> data = new HashMap<>();
data.put("alias", childAlias);
data.put("type", "generic");
data.put("description", childAlias + " flow");
@ -179,14 +179,14 @@ public class FlowTest extends AbstractAuthenticationTest {
// add execution flow to some parent flow
Map<String, String> data = new HashMap<>();
Map<String, Object> data = new HashMap<>();
data.put("alias", "SomeFlow");
data.put("type", "basic-flow");
data.put("description", "Test flow");
// This tests against a regression in KEYCLOAK-16656
data.put("provider", "registration-page-form");
Map<String, String> data2 = new HashMap<>();
Map<String, Object> data2 = new HashMap<>();
data2.put("alias", "SomeFlow2");
data2.put("type", "form-flow");
data2.put("description", "Test flow 2");
@ -265,7 +265,7 @@ public class FlowTest extends AbstractAuthenticationTest {
@Test
public void testCopyFlow() {
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "clients");
// copy using existing alias as new name
@ -321,7 +321,7 @@ public class FlowTest extends AbstractAuthenticationTest {
@Test
// KEYCLOAK-2580
public void addExecutionFlow() {
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "parent");
Response response = authMgmtResource.copy("browser", params);
Assert.assertEquals(201, response.getStatus());
@ -347,7 +347,7 @@ public class FlowTest extends AbstractAuthenticationTest {
List<AuthenticationFlowRepresentation> flows;
//copy an existing one first
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "Copy of browser");
Response response = authMgmtResource.copy("browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
@ -423,7 +423,7 @@ public class FlowTest extends AbstractAuthenticationTest {
@Test
public void editExecutionFlowTest() {
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
List<AuthenticationExecutionInfoRepresentation> executionReps;
//create new parent flow
AuthenticationFlowRepresentation newFlow = newFlow("Parent-Flow", "This is a parent flow", "basic-flow", true, false);
@ -483,6 +483,66 @@ public class FlowTest extends AbstractAuthenticationTest {
Assert.assertEquals("This is another child flow3", executionReps.get(0).getDescription());
}
@Test
public void prioritySetTest() {
//create new parent flow
AuthenticationFlowRepresentation newFlow = newFlow("Parent-Flow", "This is a parent flow", "basic-flow", true, false);
createFlow(newFlow);
HashMap<String, Object> params = new HashMap<>();
params.put("alias", "Child-Flow1");
params.put("description", "This is a child flow");
params.put("provider", "registration-page-form");
params.put("type", "basic-flow");
params.put("priority", 50);
authMgmtResource.addExecutionFlow("Parent-Flow", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
params.clear();
params.put("alias", "Child-Flow2");
params.put("description", "This is a second child flow");
params.put("provider", "registration-page-form");
params.put("type", "basic-flow");
params.put("priority", 10);
authMgmtResource.addExecutionFlow("Parent-Flow", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
params.clear();
params.put("alias", "Child-Flow3");
params.put("description", "This is a third child flow");
params.put("provider", "registration-page-form");
params.put("type", "basic-flow");
params.put("priority", 20);
authMgmtResource.addExecutionFlow("Parent-Flow", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("Parent-Flow"), params, ResourceType.AUTH_EXECUTION_FLOW);
List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("Parent-Flow");
// Verify the initial order and priority value
Assert.assertEquals("Child-Flow2", executionReps.get(0).getDisplayName());
Assert.assertEquals(10, executionReps.get(0).getPriority());
Assert.assertEquals("Child-Flow3", executionReps.get(1).getDisplayName());
Assert.assertEquals(20, executionReps.get(1).getPriority());
Assert.assertEquals("Child-Flow1", executionReps.get(2).getDisplayName());
Assert.assertEquals(50, executionReps.get(2).getPriority());
// Move last execution to the beginning
AuthenticationExecutionInfoRepresentation lastToFirst = executionReps.get(2);
lastToFirst.setPriority(5);
authMgmtResource.updateExecutions("Parent-Flow", lastToFirst);
executionReps = authMgmtResource.getExecutions("Parent-Flow");
// Verify new order and priority
Assert.assertEquals("Child-Flow1", executionReps.get(0).getDisplayName());
Assert.assertEquals(5, executionReps.get(0).getPriority());
Assert.assertEquals("Child-Flow2", executionReps.get(1).getDisplayName());
Assert.assertEquals(10, executionReps.get(1).getPriority());
Assert.assertEquals("Child-Flow3", executionReps.get(2).getDisplayName());
Assert.assertEquals(20, executionReps.get(2).getPriority());
}
@Test
public void failWithLongDescription() throws IOException {
ContainerAssume.assumeAuthServerQuarkus();
@ -525,7 +585,7 @@ public class FlowTest extends AbstractAuthenticationTest {
Assert.assertNotNull("There is no builtin flow", flow);
// adding an execution should fail
Map<String, String> data = new HashMap<>();
Map<String, Object> data = new HashMap<>();
data.put("provider", "allow-access-authenticator");
BadRequestException e = Assert.assertThrows(BadRequestException.class, () -> authMgmtResource.addExecution(flow.getAlias(), data));
OAuth2ErrorRepresentation error = e.getResponse().readEntity(OAuth2ErrorRepresentation.class);
@ -579,7 +639,7 @@ public class FlowTest extends AbstractAuthenticationTest {
}
String newFlowName = "Duplicated of " + DefaultAuthenticationFlows.BROWSER_FLOW;
Map<String, String> copyFlowParams = Map.of("newName", newFlowName);
Map<String, Object> copyFlowParams = Map.of("newName", newFlowName);
authMgmtResource.copy(existingFlow.getAlias(), copyFlowParams).close();
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), copyFlowParams, ResourceType.AUTH_FLOW);

View file

@ -129,14 +129,14 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
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[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, kerberosAuthExpectedChoices);
addExecInfo(execs, "Identity Provider Redirector", "identity-provider-redirector", true, 0, 2, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "forms", null, false, 0, 3, ALTERNATIVE, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED});
addExecInfo(execs, "Browser - Conditional OTP", null, false, 1, 1, CONDITIONAL, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Condition - user configured", "conditional-user-configured", false, 2, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
addExecInfo(execs, "OTP Form", "auth-otp-form", false, 2, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 10);
addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, kerberosAuthExpectedChoices, 20);
addExecInfo(execs, "Identity Provider Redirector", "identity-provider-redirector", true, 0, 2, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 25);
addExecInfo(execs, "forms", null, false, 0, 3, ALTERNATIVE, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 30);
addExecInfo(execs, "Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED}, 10);
addExecInfo(execs, "Browser - Conditional OTP", null, false, 1, 1, CONDITIONAL, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 20);
addExecInfo(execs, "Condition - user configured", "conditional-user-configured", false, 2, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}, 10);
addExecInfo(execs, "OTP Form", "auth-otp-form", false, 2, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 20);
expected.add(new FlowExecutions(flow, execs));
flow = newFlow("clients", "Base authentication for clients", "client-flow", true, true);
@ -146,10 +146,10 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
addExecExport(flow, null, false, "client-x509", false, null, ALTERNATIVE, 40);
execs = new LinkedList<>();
addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Signed Jwt with Client Secret", "client-secret-jwt", false, 0, 2, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "X509 Certificate", "client-x509", false, 0, 3, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 10);
addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 20);
addExecInfo(execs, "Signed Jwt with Client Secret", "client-secret-jwt", false, 0, 2, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 30);
addExecInfo(execs, "X509 Certificate", "client-x509", false, 0, 3, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 40);
expected.add(new FlowExecutions(flow, execs));
flow = newFlow("direct grant", "OpenID Connect Resource Owner Grant", "basic-flow", true, true);
@ -158,18 +158,18 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
addExecExport(flow, "Direct Grant - Conditional OTP", false, null, true, null, CONDITIONAL, 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, ALTERNATIVE,DISABLED});
addExecInfo(execs, "Direct Grant - Conditional OTP", null, false, 0, 2, CONDITIONAL, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Condition - user configured", "conditional-user-configured", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
addExecInfo(execs, "OTP", "direct-grant-validate-otp", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}, 10);
addExecInfo(execs, "Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE,DISABLED}, 20);
addExecInfo(execs, "Direct Grant - Conditional OTP", null, false, 0, 2, CONDITIONAL, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 30);
addExecInfo(execs, "Condition - user configured", "conditional-user-configured", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}, 10);
addExecInfo(execs, "OTP", "direct-grant-validate-otp", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 20);
expected.add(new FlowExecutions(flow, execs));
flow = newFlow("docker auth", "Used by Docker clients to authenticate against the IDP", "basic-flow", true, true);
addExecExport(flow, null, false, "docker-http-basic-authenticator", false, null, REQUIRED, 10);
execs = new LinkedList<>();
addExecInfo(execs, "Docker Authenticator", "docker-http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{REQUIRED});
addExecInfo(execs, "Docker Authenticator", "docker-http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}, 10);
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",
@ -178,29 +178,29 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
addExecExport(flow, "User creation or linking", false, null, true, null, REQUIRED, 20);
execs = new LinkedList<>();
addExecInfo(execs, "Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "User creation or linking", null, false, 0, 1, REQUIRED, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Create User If Unique", "idp-create-user-if-unique", true, 1, 0, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Handle Existing Account", null, false, 1, 1, ALTERNATIVE, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Confirm link existing account", "idp-confirm-link", false, 2, 0, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Account verification options", null, false, 2, 1, REQUIRED, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Verify existing account by Email", "idp-email-verification", false, 3, 0, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Verify Existing Account by Re-authentication", null, false, 3, 1, ALTERNATIVE, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 4, 0, REQUIRED, null, new String[]{REQUIRED});
addExecInfo(execs, "First broker login - Conditional OTP", null, false, 4, 1, CONDITIONAL, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Condition - user configured", "conditional-user-configured", false, 5, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
addExecInfo(execs, "OTP Form", "auth-otp-form", false, 5, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 10);
addExecInfo(execs, "User creation or linking", null, false, 0, 1, REQUIRED, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 20);
addExecInfo(execs, "Create User If Unique", "idp-create-user-if-unique", true, 1, 0, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 10);
addExecInfo(execs, "Handle Existing Account", null, false, 1, 1, ALTERNATIVE, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 20);
addExecInfo(execs, "Confirm link existing account", "idp-confirm-link", false, 2, 0, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 10);
addExecInfo(execs, "Account verification options", null, false, 2, 1, REQUIRED, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 20);
addExecInfo(execs, "Verify existing account by Email", "idp-email-verification", false, 3, 0, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 10);
addExecInfo(execs, "Verify Existing Account by Re-authentication", null, false, 3, 1, ALTERNATIVE, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 20);
addExecInfo(execs, "Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 4, 0, REQUIRED, null, new String[]{REQUIRED}, 10);
addExecInfo(execs, "First broker login - Conditional OTP", null, false, 4, 1, CONDITIONAL, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 20);
addExecInfo(execs, "Condition - user configured", "conditional-user-configured", false, 5, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}, 10);
addExecInfo(execs, "OTP Form", "auth-otp-form", false, 5, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 20);
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 Profile Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
addExecInfo(execs, "Password Validation", "registration-password-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED});
addExecInfo(execs, "reCAPTCHA", "registration-recaptcha-action", true, 1, 2, DISABLED, null, new String[]{REQUIRED, DISABLED});
addExecInfo(execs, "Terms and conditions", "registration-terms-and-conditions", false, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED});
addExecInfo(execs, "registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED}, 10);
addExecInfo(execs, "Registration User Profile Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}, 20);
addExecInfo(execs, "Password Validation", "registration-password-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}, 50);
addExecInfo(execs, "reCAPTCHA", "registration-recaptcha-action", true, 1, 2, DISABLED, null, new String[]{REQUIRED, DISABLED}, 60);
addExecInfo(execs, "Terms and conditions", "registration-terms-and-conditions", false, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}, 70);
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);
@ -210,12 +210,12 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
addExecExport(flow, "Reset - Conditional OTP", false, null, true, null, CONDITIONAL, 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, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Reset - Conditional OTP", null, false, 0, 3, CONDITIONAL, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL});
addExecInfo(execs, "Condition - user configured", "conditional-user-configured", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
addExecInfo(execs, "Reset OTP", "reset-otp", true, 1, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED});
addExecInfo(execs, "Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}, 10);
addExecInfo(execs, "Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED}, 20);
addExecInfo(execs, "Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 30);
addExecInfo(execs, "Reset - Conditional OTP", null, false, 0, 3, CONDITIONAL, true, new String[]{REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL}, 40);
addExecInfo(execs, "Condition - user configured", "conditional-user-configured", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}, 10);
addExecInfo(execs, "Reset OTP", "reset-otp", true, 1, 1, REQUIRED, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 20);
expected.add(new FlowExecutions(flow, execs));
return expected;

View file

@ -40,7 +40,7 @@ public class RegistrationFlowTest extends AbstractAuthenticationTest {
createFlow(flowRep);
// add registration execution form flow
Map<String, String> data = new HashMap<>();
Map<String, Object> data = new HashMap<>();
data.put("alias", "registrationForm2");
data.put("type", "form-flow");
data.put("description", "registrationForm2 flow");
@ -49,7 +49,7 @@ public class RegistrationFlowTest extends AbstractAuthenticationTest {
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("registration2"), data, ResourceType.AUTH_EXECUTION_FLOW);
// Should fail to add execution under top level flow
Map<String, String> data2 = new HashMap<>();
Map<String, Object> data2 = new HashMap<>();
data2.put("provider", "registration-password-action");
try {
authMgmtResource.addExecution("registration2", data2);

View file

@ -39,7 +39,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest {
public void testShiftExecution() {
// copy built-in flow so we get a new editable flow
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("newName", "Copy of browser");
Response response = authMgmtResource.copy("browser", params);
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);

View file

@ -212,7 +212,7 @@ public class CustomFlowTest extends AbstractFlowTest {
@Test
public void testRequiredAfterAlternative() {
AuthenticationManagementResource authMgmtResource = testRealm().flows();
Map<String, String> params = new HashMap();
Map<String, Object> params = new HashMap<>();
String flowAlias = "Browser Flow With Extra";
params.put("newName", flowAlias);
Response response = authMgmtResource.copy("browser", params);

View file

@ -196,7 +196,7 @@ public class DeployedScriptAuthenticatorTest extends AbstractFlowTest {
AuthenticationManagementResource authMgmtResource = adminClient.realm(TEST_REALM_NAME).flows();
// Endpoint used by admin console
Map<String, String> scriptExecution = new HashMap<>();
Map<String, Object> scriptExecution = new HashMap<>();
scriptExecution.put("provider", "script-authenticator-a.js");
// It should be possible to add another script-authenticator to the flow
@ -224,7 +224,7 @@ public class DeployedScriptAuthenticatorTest extends AbstractFlowTest {
}
// Test copy flow is OK
Map<String, String> newFlow = new HashMap<>();
Map<String, Object> newFlow = new HashMap<>();
newFlow.put("newName", "Copy of script flow");
Response resp = authMgmtResource.copy("scriptBrowser", newFlow);
Assert.assertEquals(201, resp.getStatus());

View file

@ -370,7 +370,7 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
AuthenticationFlowRepresentation copyFlow(String existingFlow, String newFlow) {
// copy that should succeed
HashMap<String, String> params = new HashMap<>();
HashMap<String, Object> params = new HashMap<>();
params.put("newName", newFlow);
Response response = authMgmtResource.copy(existingFlow, params);
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, Encode.decode(AdminEventPaths.authCopyFlowPath(existingFlow)), params, ResourceType.AUTH_FLOW);