[KEYCLOAK-12254] Fix re-evaluation of conditional flow (#6558)
This commit is contained in:
parent
106e6e15a9
commit
26458125cb
10 changed files with 430 additions and 4 deletions
|
@ -53,6 +53,8 @@ public interface CommonClientSessionModel {
|
||||||
SETUP_REQUIRED,
|
SETUP_REQUIRED,
|
||||||
ATTEMPTED,
|
ATTEMPTED,
|
||||||
SKIPPED,
|
SKIPPED,
|
||||||
CHALLENGED
|
CHALLENGED,
|
||||||
|
EVALUATED_TRUE,
|
||||||
|
EVALUATED_FALSE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -685,6 +685,18 @@ public class AuthenticationProcessor {
|
||||||
return status == AuthenticationSessionModel.ExecutionStatus.SUCCESS;
|
return status == AuthenticationSessionModel.ExecutionStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEvaluatedTrue(AuthenticationExecutionModel model) {
|
||||||
|
AuthenticationSessionModel.ExecutionStatus status = authenticationSession.getExecutionStatus().get(model.getId());
|
||||||
|
if (status == null) return false;
|
||||||
|
return status == AuthenticationSessionModel.ExecutionStatus.EVALUATED_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEvaluatedFalse(AuthenticationExecutionModel model) {
|
||||||
|
AuthenticationSessionModel.ExecutionStatus status = authenticationSession.getExecutionStatus().get(model.getId());
|
||||||
|
if (status == null) return false;
|
||||||
|
return status == AuthenticationSessionModel.ExecutionStatus.EVALUATED_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
public Response handleBrowserExceptionList(AuthenticationFlowException e) {
|
public Response handleBrowserExceptionList(AuthenticationFlowException e) {
|
||||||
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authenticationSession);
|
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authenticationSession);
|
||||||
ServicesLogger.LOGGER.failedAuthentication(e);
|
ServicesLogger.LOGGER.failedAuthentication(e);
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.util.AuthenticationFlowHistoryHelper;
|
import org.keycloak.services.util.AuthenticationFlowHistoryHelper;
|
||||||
import org.keycloak.services.util.AuthenticationFlowURLHelper;
|
import org.keycloak.services.util.AuthenticationFlowURLHelper;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
import org.keycloak.sessions.CommonClientSessionModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedHashMap;
|
import javax.ws.rs.core.MultivaluedHashMap;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
@ -374,7 +375,20 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
ConditionalAuthenticator authenticator = (ConditionalAuthenticator) createAuthenticator(factory);
|
ConditionalAuthenticator authenticator = (ConditionalAuthenticator) createAuthenticator(factory);
|
||||||
AuthenticationProcessor.Result context = processor.createAuthenticatorContext(model, authenticator, executionList);
|
AuthenticationProcessor.Result context = processor.createAuthenticatorContext(model, authenticator, executionList);
|
||||||
|
|
||||||
return !authenticator.matchCondition(context);
|
boolean matchCondition;
|
||||||
|
|
||||||
|
// Retrieve previous evaluation result if any, else evaluate and store result for future re-evaluation
|
||||||
|
if (processor.isEvaluatedTrue(model)) {
|
||||||
|
matchCondition = true;
|
||||||
|
} else if (processor.isEvaluatedFalse(model)) {
|
||||||
|
matchCondition = false;
|
||||||
|
} else {
|
||||||
|
matchCondition = authenticator.matchCondition(context);
|
||||||
|
processor.getAuthenticationSession().setExecutionStatus(model.getId(),
|
||||||
|
matchCondition ? AuthenticationSessionModel.ExecutionStatus.EVALUATED_TRUE : AuthenticationSessionModel.ExecutionStatus.EVALUATED_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !matchCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSetupRequired(AuthenticationExecutionModel model) {
|
private boolean isSetupRequired(AuthenticationExecutionModel model) {
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package org.keycloak.testsuite.authentication;
|
||||||
|
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowError;
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowException;
|
||||||
|
import org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticator;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
public class ConditionalUserAttributeValue implements ConditionalAuthenticator {
|
||||||
|
|
||||||
|
static final ConditionalUserAttributeValue SINGLETON = new ConditionalUserAttributeValue();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchCondition(AuthenticationFlowContext context) {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
// Retrieve configuration
|
||||||
|
Map<String, String> config = context.getAuthenticatorConfig().getConfig();
|
||||||
|
String attributeName = config.get(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_NAME);
|
||||||
|
String attributeValue = config.get(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_EXPECTED_VALUE);
|
||||||
|
boolean negateOutput = Boolean.parseBoolean(config.get(ConditionalUserAttributeValueFactory.CONF_NOT));
|
||||||
|
|
||||||
|
UserModel user = context.getUser();
|
||||||
|
if (user == null) {
|
||||||
|
throw new AuthenticationFlowException("authenticator: " + ConditionalUserAttributeValueFactory.PROVIDER_ID, AuthenticationFlowError.UNKNOWN_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> lstValues = user.getAttribute(attributeName);
|
||||||
|
if (lstValues != null) {
|
||||||
|
result = lstValues.contains(attributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (negateOutput) {
|
||||||
|
result = !result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void action(AuthenticationFlowContext context) {
|
||||||
|
// Not used
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUser() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
// Not used
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.keycloak.testsuite.authentication;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticator;
|
||||||
|
import org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ConditionalUserAttributeValueFactory implements ConditionalAuthenticatorFactory {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "conditional-user-attribute";
|
||||||
|
|
||||||
|
public static final String CONF_ATTRIBUTE_NAME = "attribute_name";
|
||||||
|
public static final String CONF_ATTRIBUTE_EXPECTED_VALUE = "attribute_expected_value";
|
||||||
|
public static final String CONF_NOT = "not";
|
||||||
|
|
||||||
|
private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||||
|
AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Condition - user attribute";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceCategory() {
|
||||||
|
return "condition";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfigurable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||||
|
return REQUIREMENT_CHOICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserSetupAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Flow is executed only if the user attribute exists and has the expected value";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
ProviderConfigProperty authNoteName = new ProviderConfigProperty();
|
||||||
|
authNoteName.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
authNoteName.setName(CONF_ATTRIBUTE_NAME);
|
||||||
|
authNoteName.setLabel("Attribute name");
|
||||||
|
authNoteName.setHelpText("Name of the attribute to check");
|
||||||
|
|
||||||
|
ProviderConfigProperty authNoteExpectedValue = new ProviderConfigProperty();
|
||||||
|
authNoteExpectedValue.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
authNoteExpectedValue.setName(CONF_ATTRIBUTE_EXPECTED_VALUE);
|
||||||
|
authNoteExpectedValue.setLabel("Expected attribute value");
|
||||||
|
authNoteExpectedValue.setHelpText("Expected value in the attribute");
|
||||||
|
|
||||||
|
ProviderConfigProperty negateOutput = new ProviderConfigProperty();
|
||||||
|
negateOutput.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||||
|
negateOutput.setName(CONF_NOT);
|
||||||
|
negateOutput.setLabel("Negate output");
|
||||||
|
negateOutput.setHelpText("Apply a not to the check result");
|
||||||
|
|
||||||
|
return Arrays.asList(authNoteName, authNoteExpectedValue, negateOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionalAuthenticator getSingleton() {
|
||||||
|
return ConditionalUserAttributeValue.SINGLETON;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package org.keycloak.testsuite.authentication;
|
||||||
|
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowError;
|
||||||
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SetUserAttributeAuthenticator implements Authenticator {
|
||||||
|
@Override
|
||||||
|
public void authenticate(AuthenticationFlowContext context) {
|
||||||
|
// Retrieve configuration
|
||||||
|
Map<String, String> config = context.getAuthenticatorConfig().getConfig();
|
||||||
|
String attrName = config.get(SetUserAttributeAuthenticatorFactory.CONF_ATTR_NAME);
|
||||||
|
String attrValue = config.get(SetUserAttributeAuthenticatorFactory.CONF_ATTR_VALUE);
|
||||||
|
|
||||||
|
UserModel user = context.getUser();
|
||||||
|
if (user.getAttribute(attrName) == null) {
|
||||||
|
user.setSingleAttribute(attrName, attrValue);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
List<String> attrValues = new ArrayList<>(user.getAttribute(attrName));
|
||||||
|
if (!attrValues.contains(attrValue)) {
|
||||||
|
attrValues.add(attrValue);
|
||||||
|
}
|
||||||
|
user.setAttribute(attrName, attrValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void action(AuthenticationFlowContext context) {
|
||||||
|
context.failure(AuthenticationFlowError.INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUser() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package org.keycloak.testsuite.authentication;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||||
|
import org.keycloak.authentication.authenticators.AttemptedAuthenticator;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class SetUserAttributeAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "set-attribute";
|
||||||
|
|
||||||
|
public static final String CONF_ATTR_NAME = "attr_name";
|
||||||
|
public static final String CONF_ATTR_VALUE = "attr_value";
|
||||||
|
protected static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||||
|
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||||
|
AuthenticationExecutionModel.Requirement.DISABLED};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authenticator createDisplay(KeycloakSession keycloakSession, String displayType) {
|
||||||
|
if (displayType == null) return create(keycloakSession);
|
||||||
|
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||||
|
return AttemptedAuthenticator.SINGLETON;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceCategory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfigurable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserSetupAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||||
|
return REQUIREMENT_CHOICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Set a user attribute";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope scope) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authenticator create(KeycloakSession keycloakSession) {
|
||||||
|
return new SetUserAttributeAuthenticator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Set user attribute";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
ProviderConfigProperty attributeName = new ProviderConfigProperty();
|
||||||
|
attributeName.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
attributeName.setName(CONF_ATTR_NAME);
|
||||||
|
attributeName.setLabel("Attribute name");
|
||||||
|
attributeName.setHelpText("Name of the user attribute to set");
|
||||||
|
|
||||||
|
ProviderConfigProperty attributeValue = new ProviderConfigProperty();
|
||||||
|
attributeValue.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
attributeValue.setName(CONF_ATTR_VALUE);
|
||||||
|
attributeValue.setLabel("Attribute value");
|
||||||
|
attributeValue.setHelpText("Value to set in the user attribute");
|
||||||
|
|
||||||
|
return Arrays.asList(attributeName, attributeValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,3 +21,5 @@ org.keycloak.testsuite.forms.ClickThroughAuthenticator
|
||||||
org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory
|
org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory
|
||||||
org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
|
org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
|
||||||
org.keycloak.testsuite.forms.UsernameOnlyAuthenticator
|
org.keycloak.testsuite.forms.UsernameOnlyAuthenticator
|
||||||
|
org.keycloak.testsuite.authentication.ConditionalUserAttributeValueFactory
|
||||||
|
org.keycloak.testsuite.authentication.SetUserAttributeAuthenticatorFactory
|
|
@ -205,6 +205,10 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
||||||
"Flow is executed only if user has the given role.");
|
"Flow is executed only if user has the given role.");
|
||||||
addProviderInfo(result, "conditional-user-configured", "Condition - user configured",
|
addProviderInfo(result, "conditional-user-configured", "Condition - user configured",
|
||||||
"Executes the current flow only if authenticators are configured");
|
"Executes the current flow only if authenticators are configured");
|
||||||
|
addProviderInfo(result, "conditional-user-attribute", "Condition - user attribute",
|
||||||
|
"Flow is executed only if the user attribute exists and has the expected value");
|
||||||
|
addProviderInfo(result, "set-attribute", "Set user attribute",
|
||||||
|
"Set a user attribute");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@ import org.keycloak.testsuite.pages.LoginUsernameOnlyPage;
|
||||||
import org.keycloak.testsuite.pages.PasswordPage;
|
import org.keycloak.testsuite.pages.PasswordPage;
|
||||||
import org.keycloak.testsuite.util.FlowUtil;
|
import org.keycloak.testsuite.util.FlowUtil;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.authentication.ConditionalUserAttributeValueFactory;
|
||||||
|
import org.keycloak.testsuite.authentication.SetUserAttributeAuthenticatorFactory;
|
||||||
import org.keycloak.testsuite.util.URLUtils;
|
import org.keycloak.testsuite.util.URLUtils;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
@ -444,6 +446,63 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void configureBrowserFlowWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation() {
|
||||||
|
final String newFlowAlias = "browser - changing condition";
|
||||||
|
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias));
|
||||||
|
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session)
|
||||||
|
.selectFlow(newFlowAlias)
|
||||||
|
.inForms(forms -> forms
|
||||||
|
.clear()
|
||||||
|
.addAuthenticatorExecution(Requirement.REQUIRED, UsernameFormFactory.PROVIDER_ID)
|
||||||
|
.addSubFlowExecution(Requirement.CONDITIONAL, subFlow -> {
|
||||||
|
// Add authenticators to this flow: 1 conditional authenticator and a basic authenticator executions
|
||||||
|
subFlow.addAuthenticatorExecution(Requirement.REQUIRED, ConditionalUserAttributeValueFactory.PROVIDER_ID,
|
||||||
|
config -> {
|
||||||
|
config.getConfig().put(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_NAME, "attribute");
|
||||||
|
config.getConfig().put(ConditionalUserAttributeValueFactory.CONF_ATTRIBUTE_EXPECTED_VALUE, "value");
|
||||||
|
config.getConfig().put(ConditionalUserAttributeValueFactory.CONF_NOT, Boolean.toString(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the attribute value
|
||||||
|
subFlow.addAuthenticatorExecution(Requirement.REQUIRED, SetUserAttributeAuthenticatorFactory.PROVIDER_ID,
|
||||||
|
config -> {
|
||||||
|
config.getConfig().put(SetUserAttributeAuthenticatorFactory.CONF_ATTR_NAME, "attribute");
|
||||||
|
config.getConfig().put(SetUserAttributeAuthenticatorFactory.CONF_ATTR_VALUE, "value");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Requires Password
|
||||||
|
subFlow.addAuthenticatorExecution(Requirement.REQUIRED, PasswordFormFactory.PROVIDER_ID);
|
||||||
|
|
||||||
|
// Requires TOTP
|
||||||
|
subFlow.addAuthenticatorExecution(Requirement.REQUIRED, OTPFormAuthenticatorFactory.PROVIDER_ID);
|
||||||
|
}))
|
||||||
|
.defineAsBrowserFlow()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure a conditional authenticator with a condition which change while the flow evaluation
|
||||||
|
// In such case, all the required authenticator inside the subflow should be evaluated even if the condition has changed
|
||||||
|
@Test
|
||||||
|
public void testConditionalAuthenticatorWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation() {
|
||||||
|
try {
|
||||||
|
configureBrowserFlowWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation();
|
||||||
|
|
||||||
|
// provides username
|
||||||
|
loginUsernameOnlyPage.open();
|
||||||
|
loginUsernameOnlyPage.login("user-with-two-configured-otp");
|
||||||
|
|
||||||
|
// The conditional sub flow is executed only if a specific user attribute is not set.
|
||||||
|
// This sub flow will set the user attribute and displays password form.
|
||||||
|
passwordPage.assertCurrent();
|
||||||
|
passwordPage.login("password");
|
||||||
|
|
||||||
|
Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent());
|
||||||
|
} finally {
|
||||||
|
revertFlows("browser - changing condition");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAlternativeNonInteractiveExecutorInSubflow() {
|
public void testAlternativeNonInteractiveExecutorInSubflow() {
|
||||||
final String newFlowAlias = "browser - alternative non-interactive executor";
|
final String newFlowAlias = "browser - alternative non-interactive executor";
|
||||||
|
@ -965,7 +1024,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
testRealm().flows().removeRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID);
|
testRealm().flows().removeRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID);
|
||||||
UserRepresentation user = testRealm().users().search("test-user@localhost").get(0);
|
UserRepresentation user = testRealm().users().search("test-user@localhost").get(0);
|
||||||
user.setRequiredActions(Collections.emptyList());
|
user.setRequiredActions(Collections.emptyList());
|
||||||
testRealm().users().get(user.getId()).update(user);;
|
testRealm().users().get(user.getId()).update(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue