KEYCLOAK-1967 Add support for authentication flows into admin-rest-client

This commit is contained in:
Marko Strukelj 2016-01-28 15:24:00 +01:00
parent a4ce389bf5
commit dadb470609
17 changed files with 1463 additions and 309 deletions

View file

@ -0,0 +1,77 @@
/*
* 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.representations.idm;
import java.io.Serializable;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AbstractAuthenticationExecutionRepresentation implements Serializable {
private String authenticatorConfig;
private String authenticator;
private boolean authenticatorFlow;
private String requirement;
private int priority;
public String getAuthenticatorConfig() {
return authenticatorConfig;
}
public void setAuthenticatorConfig(String authenticatorConfig) {
this.authenticatorConfig = authenticatorConfig;
}
public String getAuthenticator() {
return authenticator;
}
public void setAuthenticator(String authenticator) {
this.authenticator = authenticator;
}
public String getRequirement() {
return requirement;
}
public void setRequirement(String requirement) {
this.requirement = requirement;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
/**
* Is the referenced authenticator a flow?
*
* @return
*/
public boolean isAutheticatorFlow() {
return authenticatorFlow;
}
public void setAutheticatorFlow(boolean autheticatorFlow) {
this.authenticatorFlow = autheticatorFlow;
}
}

View file

@ -0,0 +1,50 @@
/*
* 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.representations.idm;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AuthenticationExecutionExportRepresentation extends AbstractAuthenticationExecutionRepresentation {
private String flowAlias;
private boolean userSetupAllowed;
public boolean isUserSetupAllowed() {
return userSetupAllowed;
}
public void setUserSetupAllowed(boolean userSetupAllowed) {
this.userSetupAllowed = userSetupAllowed;
}
/**
* If this execution is a flow, this is the flowId pointing to an AuthenticationFlowModel
*
* @return
*/
public String getFlowAlias() {
return flowAlias;
}
public void setFlowAlias(String flowId) {
this.flowAlias = flowId;
}
}

View file

@ -0,0 +1,128 @@
/*
* 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.representations.idm;
import java.io.Serializable;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AuthenticationExecutionInfoRepresentation implements Serializable {
protected String id;
protected String requirement;
protected String displayName;
protected List<String> requirementChoices;
protected Boolean configurable;
protected Boolean authenticationFlow;
protected String providerId;
protected String authenticationConfig;
protected String flowId;
protected int level;
protected int index;
public String getId() {
return id;
}
public void setId(String execution) {
this.id = execution;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getRequirement() {
return requirement;
}
public void setRequirement(String requirement) {
this.requirement = requirement;
}
public List<String> getRequirementChoices() {
return requirementChoices;
}
public void setRequirementChoices(List<String> requirementChoices) {
this.requirementChoices = requirementChoices;
}
public Boolean getConfigurable() {
return configurable;
}
public void setConfigurable(Boolean configurable) {
this.configurable = configurable;
}
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public String getAuthenticationConfig() {
return authenticationConfig;
}
public void setAuthenticationConfig(String authenticationConfig) {
this.authenticationConfig = authenticationConfig;
}
public Boolean getAuthenticationFlow() {
return authenticationFlow;
}
public void setAuthenticationFlow(Boolean authenticationFlow) {
this.authenticationFlow = authenticationFlow;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getFlowId() {
return flowId;
}
public void setFlowId(String flowId) {
this.flowId = flowId;
}
}

View file

@ -17,87 +17,36 @@
package org.keycloak.representations.idm;
import java.io.Serializable;
import java.util.Comparator;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AuthenticationExecutionRepresentation implements Serializable {
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AuthenticationExecutionRepresentation extends AbstractAuthenticationExecutionRepresentation {
private String authenticatorConfig;
private String authenticator;
private String flowAlias;
private boolean autheticatorFlow;
private String requirement;
private boolean userSetupAllowed;
private int priority;
private String id;
private String flowId;
private String parentFlow;
public String getAuthenticatorConfig() {
return authenticatorConfig;
public String getId() {
return id;
}
public void setAuthenticatorConfig(String authenticatorConfig) {
this.authenticatorConfig = authenticatorConfig;
public void setId(String id) {
this.id = id;
}
public String getAuthenticator() {
return authenticator;
public String getFlowId() {
return flowId;
}
public void setAuthenticator(String authenticator) {
this.authenticator = authenticator;
public void setFlowId(String flowId) {
this.flowId = flowId;
}
public String getRequirement() {
return requirement;
public String getParentFlow() {
return parentFlow;
}
public void setRequirement(String requirement) {
this.requirement = requirement;
public void setParentFlow(String parentFlow) {
this.parentFlow = parentFlow;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public boolean isUserSetupAllowed() {
return userSetupAllowed;
}
public void setUserSetupAllowed(boolean userSetupAllowed) {
this.userSetupAllowed = userSetupAllowed;
}
/**
* If this execution is a flow, this is the flowId pointing to an AuthenticationFlowModel
*
* @return
*/
public String getFlowAlias() {
return flowAlias;
}
public void setFlowAlias(String flowId) {
this.flowAlias = flowId;
}
/**
* Is the referenced authenticator a flow?
*
* @return
*/
public boolean isAutheticatorFlow() {
return autheticatorFlow;
}
public void setAutheticatorFlow(boolean autheticatorFlow) {
this.autheticatorFlow = autheticatorFlow;
}
}

View file

@ -26,12 +26,21 @@ import java.util.List;
*/
public class AuthenticationFlowRepresentation implements Serializable {
private String id;
private String alias;
private String description;
private String providerId;
private boolean topLevel;
private boolean builtIn;
protected List<AuthenticationExecutionRepresentation> authenticationExecutions;
protected List<AuthenticationExecutionExportRepresentation> authenticationExecutions;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAlias() {
return alias;
@ -73,11 +82,11 @@ public class AuthenticationFlowRepresentation implements Serializable {
this.builtIn = builtIn;
}
public List<AuthenticationExecutionRepresentation> getAuthenticationExecutions() {
public List<AuthenticationExecutionExportRepresentation> getAuthenticationExecutions() {
return authenticationExecutions;
}
public void setAuthenticationExecutions(List<AuthenticationExecutionRepresentation> authenticationExecutions) {
public void setAuthenticationExecutions(List<AuthenticationExecutionExportRepresentation> authenticationExecutions) {
this.authenticationExecutions = authenticationExecutions;
}
}

View file

@ -0,0 +1,65 @@
/*
* 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.representations.idm;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AuthenticatorConfigInfoRepresentation {
protected String name;
protected String providerId;
protected String helpText;
protected List<ConfigPropertyRepresentation> properties;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHelpText() {
return helpText;
}
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public void setHelpText(String helpText) {
this.helpText = helpText;
}
public List<ConfigPropertyRepresentation> getProperties() {
return properties;
}
public void setProperties(List<ConfigPropertyRepresentation> properties) {
this.properties = properties;
}
}

View file

@ -0,0 +1,194 @@
/*
* 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.admin.client.resource;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public interface AuthenticationManagementResource {
@GET
@Path("/form-providers")
@Produces(MediaType.APPLICATION_JSON)
List<Map<String, Object>> getFormProviders();
@Path("/authenticator-providers")
@GET
@Produces(MediaType.APPLICATION_JSON)
List<Map<String, Object>> getAuthenticatorProviders();
@Path("/client-authenticator-providers")
@GET
@Produces(MediaType.APPLICATION_JSON)
List<Map<String, Object>> getClientAuthenticatorProviders();
@Path("/form-action-providers")
@GET
@Produces(MediaType.APPLICATION_JSON)
List<Map<String, Object>> getFormActionProviders();
@Path("/flows")
@GET
@Produces(MediaType.APPLICATION_JSON)
List<AuthenticationFlowRepresentation> getFlows();
@Path("/flows")
@POST
@Consumes(MediaType.APPLICATION_JSON)
Response createFlow(AuthenticationFlowRepresentation model);
@Path("/flows/{id}")
@GET
@Produces(MediaType.APPLICATION_JSON)
AuthenticationFlowRepresentation getFlow(@PathParam("id") String id);
@Path("/flows/{id}")
@DELETE
void deleteFlow(@PathParam("id") String id);
@Path("/flows/{flowAlias}/copy")
@POST
@Consumes(MediaType.APPLICATION_JSON)
Response copy(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
@Path("/flows/{flowAlias}/executions/flow")
@POST
@Consumes(MediaType.APPLICATION_JSON)
void addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
@Path("/flows/{flowAlias}/executions/execution")
@POST
@Consumes(MediaType.APPLICATION_JSON)
void addExecution(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
@Path("/flows/{flowAlias}/executions")
@GET
@Produces(MediaType.APPLICATION_JSON)
Response getExecutions(@PathParam("flowAlias") String flowAlias);
@Path("/flows/{flowAlias}/executions")
@PUT
@Consumes(MediaType.APPLICATION_JSON)
void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionInfoRepresentation rep);
@Path("/executions")
@POST
@Consumes(MediaType.APPLICATION_JSON)
Response addExecution(AuthenticationExecutionRepresentation model);
@Path("/executions/{executionId}/raise-priority")
@POST
void raisePriority(@PathParam("executionId") String execution);
@Path("/executions/{executionId}/lower-priority")
@POST
void lowerPriority(@PathParam("executionId") String execution);
@Path("/executions/{executionId}")
@DELETE
void removeExecution(@PathParam("executionId") String execution);
@Path("/executions/{executionId}/config")
@POST
@Consumes(MediaType.APPLICATION_JSON)
Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation config);
@Path("/executions/{executionId}/config/{id}")
@GET
@Produces(MediaType.APPLICATION_JSON)
AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id);
@Path("unregistered-required-actions")
@GET
@Produces(MediaType.APPLICATION_JSON)
List<Map<String, String>> getUnregisteredRequiredActions();
@Path("register-required-action")
@POST
@Consumes(MediaType.APPLICATION_JSON)
void registereRequiredAction(Map<String, String> data);
@Path("required-actions")
@GET
@Produces(MediaType.APPLICATION_JSON)
List<RequiredActionProviderRepresentation> getRequiredActions();
@Path("required-actions/{alias}")
@GET
@Produces(MediaType.APPLICATION_JSON)
RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias);
@Path("required-actions/{alias}")
@PUT
@Consumes(MediaType.APPLICATION_JSON)
void updateRequiredAction(@PathParam("alias") String alias, RequiredActionProviderRepresentation rep);
@Path("required-actions/{alias}")
@DELETE
void updateRequiredAction(@PathParam("alias") String alias);
@Path("config-description/{providerId}")
@GET
@Produces(MediaType.APPLICATION_JSON)
AuthenticatorConfigInfoRepresentation getAuthenticatorConfigDescription(@PathParam("providerId") String providerId);
@Path("per-client-config-description")
@GET
@Produces(MediaType.APPLICATION_JSON)
Map<String, List<ConfigPropertyRepresentation>> getPerClientConfigDescription();
@Path("config")
@POST
@Consumes(MediaType.APPLICATION_JSON)
Response createAuthenticatorConfig(AuthenticatorConfigRepresentation config);
@Path("config/{id}")
@GET
@Produces(MediaType.APPLICATION_JSON)
AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("id") String id);
@Path("config/{id}")
@DELETE
void removeAuthenticatorConfig(@PathParam("id") String id);
@Path("config/{id}")
@PUT
@Consumes(MediaType.APPLICATION_JSON)
void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation config);
}

View file

@ -142,4 +142,8 @@ public interface RealmResource {
@Path("clients-initial-access")
ClientInitialAccessResource clientInitialAccess();
@Path("authentication")
@Consumes(MediaType.APPLICATION_JSON)
AuthenticationManagementResource flows();
}

View file

@ -46,7 +46,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.AuthDetailsRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
@ -680,12 +680,13 @@ public class ModelToRepresentation {
public static AuthenticationFlowRepresentation toRepresentation(RealmModel realm, AuthenticationFlowModel model) {
AuthenticationFlowRepresentation rep = new AuthenticationFlowRepresentation();
rep.setId(model.getId());
rep.setBuiltIn(model.isBuiltIn());
rep.setTopLevel(model.isTopLevel());
rep.setProviderId(model.getProviderId());
rep.setAlias(model.getAlias());
rep.setDescription(model.getDescription());
rep.setAuthenticationExecutions(new LinkedList<AuthenticationExecutionRepresentation>());
rep.setAuthenticationExecutions(new LinkedList<AuthenticationExecutionExportRepresentation>());
for (AuthenticationExecutionModel execution : realm.getAuthenticationExecutions(model.getId())) {
rep.getAuthenticationExecutions().add(toRepresentation(realm, execution));
}
@ -693,8 +694,8 @@ public class ModelToRepresentation {
}
public static AuthenticationExecutionRepresentation toRepresentation(RealmModel realm, AuthenticationExecutionModel model) {
AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
public static AuthenticationExecutionExportRepresentation toRepresentation(RealmModel realm, AuthenticationExecutionModel model) {
AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation();
if (model.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(model.getAuthenticatorConfig());
rep.setAuthenticatorConfig(config.getAlias());

View file

@ -48,7 +48,9 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
@ -65,13 +67,13 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.RolesRepresentation;
import org.keycloak.representations.idm.ScopeMappingRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserFederationMapperRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.common.util.UriUtils;
import java.io.IOException;
import java.util.ArrayList;
@ -83,7 +85,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.keycloak.representations.idm.RolesRepresentation;
public class RepresentationToModel {
@ -442,11 +443,13 @@ public class RepresentationToModel {
}
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
AuthenticationFlowModel model = toModel(flowRep);
// make sure new id is generated for new AuthenticationFlowModel instance
model.setId(null);
model = newRealm.addAuthenticationFlow(model);
}
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias());
for (AuthenticationExecutionRepresentation exeRep : flowRep.getAuthenticationExecutions()) {
for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) {
AuthenticationExecutionModel execution = toModel(newRealm, exeRep);
execution.setParentFlow(model.getId());
newRealm.addAuthenticatorExecution(execution);
@ -1464,6 +1467,7 @@ public class RepresentationToModel {
public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
AuthenticationFlowModel model = new AuthenticationFlowModel();
model.setId(rep.getId());
model.setBuiltIn(rep.isBuiltIn());
model.setTopLevel(rep.isTopLevel());
model.setProviderId(rep.getProviderId());
@ -1473,7 +1477,7 @@ public class RepresentationToModel {
}
public static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationExecutionRepresentation rep) {
public static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationExecutionExportRepresentation rep) {
AuthenticationExecutionModel model = new AuthenticationExecutionModel();
if (rep.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel config = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig());
@ -1490,6 +1494,24 @@ public class RepresentationToModel {
return model;
}
public static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationExecutionRepresentation rep) {
AuthenticationExecutionModel model = new AuthenticationExecutionModel();
model.setId(rep.getId());
model.setFlowId(rep.getFlowId());
model.setAuthenticator(rep.getAuthenticator());
model.setPriority(rep.getPriority());
model.setParentFlow(rep.getParentFlow());
model.setAuthenticatorFlow(rep.isAutheticatorFlow());
model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));
if (rep.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel cfg = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig());
model.setAuthenticatorConfig(cfg.getId());
}
return model;
}
public static AuthenticatorConfigModel toModel(AuthenticatorConfigRepresentation rep) {
AuthenticatorConfigModel model = new AuthenticatorConfigModel();
model.setAlias(rep.getAlias());

View file

@ -34,9 +34,17 @@ import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ServicesLogger;
import org.keycloak.utils.CredentialHelper;
@ -83,108 +91,6 @@ public class AuthenticationManagementResource {
this.adminEvent = adminEvent;
}
public static class AuthenticationExecutionRepresentation {
protected String id;
protected String requirement;
protected String displayName;
protected List<String> requirementChoices;
protected Boolean configurable;
protected Boolean authenticationFlow;
protected String providerId;
protected String authenticationConfig;
protected String flowId;
protected int level;
protected int index;
public String getId() {
return id;
}
public void setId(String execution) {
this.id = execution;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getRequirement() {
return requirement;
}
public void setRequirement(String requirement) {
this.requirement = requirement;
}
public List<String> getRequirementChoices() {
return requirementChoices;
}
public void setRequirementChoices(List<String> requirementChoices) {
this.requirementChoices = requirementChoices;
}
public Boolean getConfigurable() {
return configurable;
}
public void setConfigurable(Boolean configurable) {
this.configurable = configurable;
}
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public String getAuthenticationConfig() {
return authenticationConfig;
}
public void setAuthenticationConfig(String authenticationConfig) {
this.authenticationConfig = authenticationConfig;
}
public Boolean getAuthenticationFlow() {
return authenticationFlow;
}
public void setAuthenticationFlow(Boolean authenticationFlow) {
this.authenticationFlow = authenticationFlow;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getFlowId() {
return flowId;
}
public void setFlowId(String flowId) {
this.flowId = flowId;
}
}
/**
* Get form providers
*
@ -269,12 +175,12 @@ public class AuthenticationManagementResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<AuthenticationFlowModel> getFlows() {
public List<AuthenticationFlowRepresentation> getFlows() {
this.auth.requireView();
List<AuthenticationFlowModel> flows = new LinkedList<>();
List<AuthenticationFlowRepresentation> flows = new LinkedList<>();
for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
if (flow.isTopLevel()) {
flows.add(flow);
flows.add(ModelToRepresentation.toRepresentation(realm, flow));
}
}
return flows;
@ -283,27 +189,26 @@ public class AuthenticationManagementResource {
/**
* Create a new authentication flow
*
* @param model Authentication flow model
* @param flow Authentication flow representation
* @return
*/
@Path("/flows")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
public Response createFlow(AuthenticationFlowModel model) {
public Response createFlow(AuthenticationFlowRepresentation flow) {
this.auth.requireManage();
if (model.getAlias() == null || model.getAlias().isEmpty()) {
if (flow.getAlias() == null || flow.getAlias().isEmpty()) {
return ErrorResponse.exists("Failed to create flow with empty alias name");
}
if (realm.getFlowByAlias(model.getAlias()) != null) {
return ErrorResponse.exists("Flow " + model.getAlias() + " already exists");
if (realm.getFlowByAlias(flow.getAlias()) != null) {
return ErrorResponse.exists("Flow " + flow.getAlias() + " already exists");
}
realm.addAuthenticationFlow(model);
realm.addAuthenticationFlow(RepresentationToModel.toModel(flow));
return Response.status(201).build();
}
/**
@ -316,14 +221,14 @@ public class AuthenticationManagementResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public AuthenticationFlowModel getFlow(@PathParam("id") String id) {
public AuthenticationFlowRepresentation getFlow(@PathParam("id") String id) {
this.auth.requireView();
AuthenticationFlowModel flow = realm.getAuthenticationFlowById(id);
if (flow == null) {
throw new NotFoundException("Could not find flow with id");
}
return flow;
return ModelToRepresentation.toRepresentation(realm, flow);
}
/**
@ -478,8 +383,16 @@ public class AuthenticationManagementResource {
if (parentFlow == null) {
throw new BadRequestException("Parent flow doesn't exists");
}
if (parentFlow.isBuiltIn()) {
throw new BadRequestException("It is illegal to add execution to a built in flow");
}
String provider = data.get("provider");
// make sure provider is one of the registered providers
ProviderFactory f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider);
if (f == null) {
throw new BadRequestException("No authentication provider found for id: " + provider);
}
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(parentFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
@ -507,7 +420,7 @@ public class AuthenticationManagementResource {
logger.debug("flow not found: " + flowAlias);
return Response.status(NOT_FOUND).build();
}
List<AuthenticationExecutionRepresentation> result = new LinkedList<>();
List<AuthenticationExecutionInfoRepresentation> result = new LinkedList<>();
int level = 0;
@ -515,11 +428,11 @@ public class AuthenticationManagementResource {
return Response.ok(result).build();
}
public void recurseExecutions(AuthenticationFlowModel flow, List<AuthenticationExecutionRepresentation> result, int level) {
public void recurseExecutions(AuthenticationFlowModel flow, List<AuthenticationExecutionInfoRepresentation> result, int level) {
int index = 0;
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flow.getId());
for (AuthenticationExecutionModel execution : executions) {
AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
AuthenticationExecutionInfoRepresentation rep = new AuthenticationExecutionInfoRepresentation();
rep.setLevel(level);
rep.setIndex(index++);
rep.setRequirementChoices(new LinkedList<String>());
@ -575,7 +488,7 @@ public class AuthenticationManagementResource {
@PUT
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
public void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionRepresentation rep) {
public void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionInfoRepresentation rep) {
this.auth.requireManage();
AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
@ -599,14 +512,15 @@ public class AuthenticationManagementResource {
/**
* Add new authentication execution
*
* @param model JSON model describing authentication execution
* @param execution JSON model describing authentication execution
*/
@Path("/executions")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
public Response addExecution(AuthenticationExecutionModel model) {
public Response addExecution(AuthenticationExecutionRepresentation execution) {
this.auth.requireManage();
AuthenticationExecutionModel model = RepresentationToModel.toModel(realm, execution);
AuthenticationFlowModel parentFlow = getParentFlow(model);
if (parentFlow.isBuiltIn()) {
throw new BadRequestException("It is illegal to add execution to a built in flow");
@ -745,14 +659,14 @@ public class AuthenticationManagementResource {
* Update execution with new configuration
*
* @param execution Execution id
* @param config JSON with new configuration
* @param json JSON with new configuration
* @return
*/
@Path("/executions/{executionId}/config")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
public Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigModel config) {
public Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation json) {
this.auth.requireManage();
AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
@ -761,6 +675,7 @@ public class AuthenticationManagementResource {
throw new NotFoundException("Illegal execution");
}
AuthenticatorConfigModel config = RepresentationToModel.toModel(json);
config = realm.addAuthenticatorConfig(config);
model.setAuthenticatorConfig(config.getId());
realm.updateAuthenticatorExecution(model);
@ -777,63 +692,14 @@ public class AuthenticationManagementResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public AuthenticatorConfigModel getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id) {
public AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id) {
this.auth.requireView();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
if (config == null) {
throw new NotFoundException("Could not find authenticator config");
}
return config;
}
public static class RequiredActionProviderRepresentation {
private String alias;
private String name;
private boolean enabled;
private boolean defaultAction;
private Map<String, String> config = new HashMap<String, String>();
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isDefaultAction() {
return defaultAction;
}
public void setDefaultAction(boolean defaultAction) {
this.defaultAction = defaultAction;
}
public Map<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
return ModelToRepresentation.toRepresentation(config);
}
/**
@ -976,47 +842,6 @@ public class AuthenticationManagementResource {
realm.removeRequiredActionProvider(model);
}
public class AuthenticatorConfigDescription {
protected String name;
protected String providerId;
protected String helpText;
protected List<ConfigPropertyRepresentation> properties;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHelpText() {
return helpText;
}
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public void setHelpText(String helpText) {
this.helpText = helpText;
}
public List<ConfigPropertyRepresentation> getProperties() {
return properties;
}
public void setProperties(List<ConfigPropertyRepresentation> properties) {
this.properties = properties;
}
}
/**
* Get authenticator provider's configuration description
*/
@ -1024,13 +849,13 @@ public class AuthenticationManagementResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public AuthenticatorConfigDescription getAuthenticatorConfigDescription(@PathParam("providerId") String providerId) {
public AuthenticatorConfigInfoRepresentation getAuthenticatorConfigDescription(@PathParam("providerId") String providerId) {
this.auth.requireView();
ConfigurableAuthenticatorFactory factory = CredentialHelper.getConfigurableAuthenticatorFactory(session, providerId);
if (factory == null) {
throw new NotFoundException("Could not find authenticator provider");
}
AuthenticatorConfigDescription rep = new AuthenticatorConfigDescription();
AuthenticatorConfigInfoRepresentation rep = new AuthenticatorConfigInfoRepresentation();
rep.setProviderId(providerId);
rep.setName(factory.getDisplayType());
rep.setHelpText(factory.getHelpText());
@ -1084,14 +909,14 @@ public class AuthenticationManagementResource {
/**
* Create new authenticator configuration
* @param config JSON describing new authenticator configuration
* @param rep JSON describing new authenticator configuration
*/
@Path("config")
@POST
@NoCache
public Response createAuthenticatorConfig(AuthenticatorConfigModel config) {
public Response createAuthenticatorConfig(AuthenticatorConfigRepresentation rep) {
this.auth.requireManage();
config = realm.addAuthenticatorConfig(config);
AuthenticatorConfigModel config = realm.addAuthenticatorConfig(RepresentationToModel.toModel(rep));
return Response.created(uriInfo.getAbsolutePathBuilder().path(config.getId()).build()).build();
}
@ -1103,14 +928,14 @@ public class AuthenticationManagementResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public AuthenticatorConfigModel getAuthenticatorConfig(@PathParam("id") String id) {
public AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("id") String id) {
this.auth.requireView();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
if (config == null) {
throw new NotFoundException("Could not find authenticator config");
}
return config;
return ModelToRepresentation.toRepresentation(config);
}
/**
@ -1143,25 +968,21 @@ public class AuthenticationManagementResource {
/**
* Update authenticator configuration
* @param id Configuration id
* @param config JSON describing new state of authenticator configuration
* @param rep JSON describing new state of authenticator configuration
*/
@Path("config/{id}")
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@NoCache
public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigModel config) {
public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation rep) {
this.auth.requireManage();
AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id);
if (exists == null) {
throw new NotFoundException("Could not find authenticator config");
}
exists.setAlias(config.getAlias());
exists.setConfig(config.getConfig());
exists.setAlias(rep.getAlias());
exists.setConfig(rep.getConfig());
realm.updateAuthenticatorConfig(exists);
}
}

View file

@ -0,0 +1,151 @@
/*
* 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.authentication;
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.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
static final String REALM_NAME = "test";
static final String REQUIRED = "REQUIRED";
static final String OPTIONAL = "OPTIONAL";
static final String DISABLED = "DISABLED";
static final String ALTERNATIVE = "ALTERNATIVE";
RealmResource realmResource;
AuthenticationManagementResource authMgmtResource;
@Before
public void before() {
realmResource = adminClient.realms().realm(REALM_NAME);
authMgmtResource = realmResource.flows();
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation testRealmRep = new RealmRepresentation();
testRealmRep.setRealm(REALM_NAME);
testRealmRep.setEnabled(true);
testRealms.add(testRealmRep);
}
AuthenticationExecutionInfoRepresentation findExecutionByProvider(String provider, List<AuthenticationExecutionInfoRepresentation> executions) {
for (AuthenticationExecutionInfoRepresentation exec : executions) {
if (provider.equals(exec.getProviderId())) {
return exec;
}
}
return null;
}
AuthenticationFlowRepresentation findFlowByAlias(String alias, List<AuthenticationFlowRepresentation> flows) {
for (AuthenticationFlowRepresentation flow : flows) {
if (alias.equals(flow.getAlias())) {
return flow;
}
}
return null;
}
void compareExecution(AuthenticationExecutionInfoRepresentation expected, AuthenticationExecutionInfoRepresentation actual) {
Assert.assertEquals("Execution requirement - " + actual.getProviderId(), expected.getRequirement(), actual.getRequirement());
Assert.assertEquals("Execution display name - " + actual.getProviderId(), expected.getDisplayName(), actual.getDisplayName());
Assert.assertEquals("Execution configurable - " + actual.getProviderId(), expected.getConfigurable(), actual.getConfigurable());
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 authentication flow - " + actual.getProviderId(), expected.getAuthenticationFlow(), actual.getAuthenticationFlow());
Assert.assertEquals("Execution requirement choices - " + actual.getProviderId(), expected.getRequirementChoices(), actual.getRequirementChoices());
}
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());
}
AuthenticationFlowRepresentation newFlow(String alias, String description,
String providerId, boolean topLevel, boolean builtIn) {
AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation();
flow.setAlias(alias);
flow.setDescription(description);
flow.setProviderId(providerId);
flow.setTopLevel(topLevel);
flow.setBuiltIn(builtIn);
return flow;
}
AuthenticationExecutionInfoRepresentation newExecution(String displayName, String providerId, Boolean configurable,
int level, int index, String requirement, Boolean authFlow, String[] choices) {
AuthenticationExecutionInfoRepresentation execution = new AuthenticationExecutionInfoRepresentation();
execution.setRequirement(requirement);
execution.setDisplayName(displayName);
execution.setConfigurable(configurable);
execution.setProviderId(providerId);
execution.setLevel(level);
execution.setIndex(index);
execution.setAuthenticationFlow(authFlow);
if (choices != null) {
execution.setRequirementChoices(Arrays.asList(choices));
}
return execution;
}
AuthenticatorConfigRepresentation newConfig(String alias, String[] keyvalues) {
AuthenticatorConfigRepresentation config = new AuthenticatorConfigRepresentation();
config.setAlias(alias);
if (keyvalues == null) {
throw new IllegalArgumentException("keyvalues == null");
}
if (keyvalues.length % 2 != 0) {
throw new IllegalArgumentException("keyvalues should have even number of elements");
}
LinkedHashMap<String, String> params = new LinkedHashMap<>();
for (int i = 0; i < keyvalues.length; i += 2) {
params.put(keyvalues[i], keyvalues[i + 1]);
}
config.setConfig(params);
return config;
}
}

View file

@ -0,0 +1,173 @@
/*
* 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.authentication;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class ExecutionTest extends AbstractAuthenticationTest {
@Test
public void testAddRemoveExecution() {
// try add execution to built-in flow
HashMap<String, String> params = new HashMap<>();
params.put("provider", "idp-review-profile");
try {
authMgmtResource.addExecution("browser", params);
Assert.fail("add execution to built-in flow should fail");
} catch (BadRequestException expected) {
}
// copy built-in flow so we get a new editable flow
params.put("newName", "Copy of browser");
Response response = authMgmtResource.copy("browser", params);
try {
Assert.assertEquals("Copy flow", 201, response.getStatus());
} finally {
response.close();
}
// add execution using inexistent provider
params.put("provider", "test-execution");
try {
authMgmtResource.addExecution("Copy of browser", params);
Assert.fail("add execution with inexistent provider should fail");
} catch(BadRequestException expected) {
}
// add execution - should succeed
params.put("provider", "idp-review-profile");
authMgmtResource.addExecution("Copy of browser", params);
// check execution was added
response = authMgmtResource.getExecutions("Copy of browser");
AuthenticationExecutionInfoRepresentation exec;
AuthenticationExecutionInfoRepresentation authCookieExec;
try {
List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
exec = findExecutionByProvider("idp-review-profile", executionReps);
Assert.assertNotNull("idp-review-profile added", exec);
// we'll need auth-cookie later
authCookieExec = findExecutionByProvider("auth-cookie", executionReps);
} finally {
response.close();
}
compareExecution(newExecution("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
// remove execution
authMgmtResource.removeExecution(exec.getId());
// check execution was removed
response = authMgmtResource.getExecutions("Copy of browser");
try {
List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
exec = findExecutionByProvider("idp-review-profile", executionReps);
Assert.assertNull("idp-review-profile removed", exec);
} finally {
response.close();
}
// now add the execution again using a different method and representation
// delete auth-cookie
authMgmtResource.removeExecution(authCookieExec.getId());
AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
rep.setPriority(10);
rep.setAuthenticator("auth-cookie");
rep.setRequirement(OPTIONAL);
response = authMgmtResource.addExecution(rep);
try {
Assert.assertEquals("added execution missing parent flow", 400, response.getStatus());
} finally {
response.close();
}
// get Copy of browser flow id, and set it on execution
List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation flow = findFlowByAlias("Copy of browser", flows);
rep.setParentFlow(flow.getId());
// add execution - should succeed
response = authMgmtResource.addExecution(rep);
try {
Assert.assertEquals("added execution", 201, response.getStatus());
} finally {
response.close();
}
// check execution was added
List<AuthenticationExecutionInfoRepresentation> executions;
response = authMgmtResource.getExecutions("Copy of browser");
try {
executions = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
exec = findExecutionByProvider("auth-cookie", executions);
Assert.assertNotNull("auth-cookie added", exec);
} finally {
response.close();
}
// 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);
}
@Test
public void testUpdateExecution() {
// get current auth-cookie execution
Response response = authMgmtResource.getExecutions("browser");
List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(
new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("auth-cookie", executionReps);
Assert.assertEquals("auth-cookie set to ALTERNATIVE", ALTERNATIVE, exec.getRequirement());
// switch from DISABLED to ALTERNATIVE
exec.setRequirement(DISABLED);
authMgmtResource.updateExecutions("browser", exec);
// make sure the change is visible
response = authMgmtResource.getExecutions("browser");
executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {});
// get current auth-cookie execution
AuthenticationExecutionInfoRepresentation exec2 = findExecutionByProvider("auth-cookie", executionReps);
compareExecution(exec, exec2);
}
}

View file

@ -0,0 +1,128 @@
/*
* 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.authentication;
import org.junit.Assert;
import org.junit.Test;
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;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class FlowTest extends AbstractAuthenticationTest {
@Test
public void testAddRemoveFlow() {
// test that built-in flow cannot be deleted
List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
for (AuthenticationFlowRepresentation flow : flows) {
try {
authMgmtResource.deleteFlow(flow.getId());
Assert.fail("deleteFlow should fail for built in flow");
} catch (BadRequestException e) {
break;
}
}
// try create new flow using alias of already existing flow
Response response = authMgmtResource.createFlow(newFlow("browser", "Browser flow", "basic-flow", true, false));
try {
Assert.assertEquals("createFlow using the alias of existing flow should fail", 409, response.getStatus());
} finally {
response.close();
}
// create new flow that should succeed
AuthenticationFlowRepresentation newFlow = newFlow("browser-2", "Browser flow", "basic-flow", true, false);
response = authMgmtResource.createFlow(newFlow);
try {
Assert.assertEquals("createFlow success", 201, response.getStatus());
} finally {
response.close();
}
// check that new flow is returned
flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation found = findFlowByAlias("browser-2", flows);
Assert.assertNotNull("created flow visible", found);
compareFlows(newFlow, found);
// delete non-built-in flow
authMgmtResource.deleteFlow(found.getId());
// check the deleted flow is no longer returned
flows = authMgmtResource.getFlows();
found = findFlowByAlias("browser-2", flows);
Assert.assertNull("flow deleted", found);
}
@Test
public void testCopyFlow() {
HashMap<String, String> params = new HashMap<>();
params.put("newName", "clients");
// copy using existing alias as new name
Response response = authMgmtResource.copy("browser", params);
try {
Assert.assertEquals("Copy flow using the new alias of existing flow should fail", 409, response.getStatus());
} finally {
response.close();
}
// copy non-existing flow
params.clear();
response = authMgmtResource.copy("non-existent", params);
try {
Assert.assertEquals("Copy non-existing flow", 404, response.getStatus());
} finally {
response.close();
}
// copy that should succeed
params.put("newName", "Copy of browser");
response = authMgmtResource.copy("browser", params);
try {
Assert.assertEquals("Copy flow", 201, response.getStatus());
} finally {
response.close();
}
// compare original flow with a copy - fields should be the same except id, alias, and builtIn
List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation browser = findFlowByAlias("browser", flows);
AuthenticationFlowRepresentation copyOfBrowser = findFlowByAlias("Copy of browser", flows);
Assert.assertNotNull(browser);
Assert.assertNotNull(copyOfBrowser);
// adjust expected values before comparing
browser.setAlias("Copy of browser");
browser.setBuiltIn(false);
compareFlows(browser, copyOfBrowser);
}
}

View file

@ -0,0 +1,200 @@
/*
* 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.authentication;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
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
Response executions = authMgmtResource.getExecutions(flow.getAlias());
List<AuthenticationExecutionInfoRepresentation> executionReps = executions.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
for (AuthenticationExecutionInfoRepresentation exec : executionReps) {
// separately load referenced configurations
String configId = exec.getAuthenticationConfig();
if (configId != null && !configs.containsKey(configId)) {
configs.put(configId, authMgmtResource.getAuthenticatorConfig(exec.getId(), 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);
compareExecutions(fe1.executions, fe2.executions);
}
}
private void compareExecutions(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);
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));
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));
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));
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));
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));
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));
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));
return expected;
}
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());
}
}
}

View file

@ -0,0 +1,101 @@
/*
* 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.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,81 @@
/*
* 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.authentication;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class ShiftExecutionTest extends AbstractAuthenticationTest {
@Test
public void testShiftExecution() {
// copy built-in flow so we get a new editable flow
HashMap<String, String> params = new HashMap<>();
params.put("newName", "Copy of browser");
Response response = authMgmtResource.copy("browser", params);
try {
Assert.assertEquals("Copy flow", 201, response.getStatus());
} finally {
response.close();
}
// get executions
response = authMgmtResource.getExecutions("Copy of browser");
List<AuthenticationExecutionInfoRepresentation> executions = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
AuthenticationExecutionInfoRepresentation last = executions.get(executions.size() - 1);
AuthenticationExecutionInfoRepresentation oneButLast = executions.get(executions.size() - 2);
// shift last execution up
authMgmtResource.raisePriority(last.getId());
response = authMgmtResource.getExecutions("Copy of browser");
List<AuthenticationExecutionInfoRepresentation> executions2 = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
AuthenticationExecutionInfoRepresentation last2 = executions2.get(executions.size() - 1);
AuthenticationExecutionInfoRepresentation oneButLast2 = executions2.get(executions.size() - 2);
Assert.assertEquals("Execution shifted up - N", last.getId(), oneButLast2.getId());
Assert.assertEquals("Execution shifted up - N-1", oneButLast.getId(), last2.getId());
// shift one before last down
authMgmtResource.lowerPriority(oneButLast2.getId());
response = authMgmtResource.getExecutions("Copy of browser");
executions2 = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
last2 = executions2.get(executions.size() - 1);
oneButLast2 = executions2.get(executions.size() - 2);
Assert.assertEquals("Execution shifted down - N", last.getId(), last2.getId());
Assert.assertEquals("Execution shifted down - N-1", oneButLast.getId(), oneButLast2.getId());
}
}