Search in sources :

Example 1 with AuthenticationExecutionModel

use of org.keycloak.models.AuthenticationExecutionModel in project keycloak by keycloak.

the class DefaultAuthenticationFlow method processResult.

public Response processResult(AuthenticationProcessor.Result result, boolean isAction) {
    AuthenticationExecutionModel execution = result.getExecution();
    FlowStatus status = result.getStatus();
    switch(status) {
        case SUCCESS:
            logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
            setExecutionStatus(execution, AuthenticationSessionModel.ExecutionStatus.SUCCESS);
            return null;
        case FAILED:
            logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
            processor.logFailure();
            setExecutionStatus(execution, AuthenticationSessionModel.ExecutionStatus.FAILED);
            if (result.getChallenge() != null) {
                return sendChallenge(result, execution);
            }
            throw new AuthenticationFlowException(result.getError(), result.getEventDetails(), result.getUserErrorMessage());
        case FORK:
            logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
            processor.getAuthenticationSession().setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
            throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
        case FORCE_CHALLENGE:
        case CHALLENGE:
            setExecutionStatus(execution, AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
            return sendChallenge(result, execution);
        case FAILURE_CHALLENGE:
            logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
            processor.logFailure();
            setExecutionStatus(execution, AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
            return sendChallenge(result, execution);
        case ATTEMPTED:
            logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
            setExecutionStatus(execution, AuthenticationSessionModel.ExecutionStatus.ATTEMPTED);
            return null;
        case FLOW_RESET:
            processor.resetFlow();
            return processor.authenticate();
        default:
            logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
            ServicesLogger.LOGGER.unknownResultStatus();
            throw new AuthenticationFlowException(AuthenticationFlowError.INTERNAL_ERROR);
    }
}
Also used : AuthenticationExecutionModel(org.keycloak.models.AuthenticationExecutionModel)

Example 2 with AuthenticationExecutionModel

use of org.keycloak.models.AuthenticationExecutionModel in project keycloak by keycloak.

the class DefaultAuthenticationFlow method processFlow.

@Override
public Response processFlow() {
    logger.debugf("processFlow: %s", flow.getAlias());
    // separate flow elements into required and alternative elements
    List<AuthenticationExecutionModel> requiredList = new ArrayList<>();
    List<AuthenticationExecutionModel> alternativeList = new ArrayList<>();
    fillListsOfExecutions(executions.stream(), requiredList, alternativeList);
    // handle required elements : all required elements need to be executed
    boolean requiredElementsSuccessful = true;
    Iterator<AuthenticationExecutionModel> requiredIListIterator = requiredList.listIterator();
    while (requiredIListIterator.hasNext()) {
        AuthenticationExecutionModel required = requiredIListIterator.next();
        // If the flow has been processed before it will not be removed to consider its execution status.
        if (required.isConditional() && !isProcessed(required) && isConditionalSubflowDisabled(required)) {
            requiredIListIterator.remove();
            continue;
        }
        Response response = processSingleFlowExecutionModel(required, true);
        requiredElementsSuccessful &= processor.isSuccessful(required) || isSetupRequired(required);
        if (response != null) {
            return response;
        }
        // We can break as we know that the whole subflow would be considered unsuccessful as well
        if (!requiredElementsSuccessful) {
            break;
        }
    }
    // Evaluate alternative elements only if there are no required elements. This may also occur if there was only condition elements
    if (requiredList.isEmpty()) {
        // check if an alternative is already successful, in case we are returning in the flow after an action
        if (alternativeList.stream().anyMatch(alternative -> processor.isSuccessful(alternative) || isSetupRequired(alternative))) {
            return onFlowExecutionsSuccessful();
        }
        // handle alternative elements: the first alternative element to be satisfied is enough
        for (AuthenticationExecutionModel alternative : alternativeList) {
            try {
                Response response = processSingleFlowExecutionModel(alternative, true);
                if (response != null) {
                    return response;
                }
                if (processor.isSuccessful(alternative) || isSetupRequired(alternative)) {
                    return onFlowExecutionsSuccessful();
                }
            } catch (AuthenticationFlowException afe) {
                // consuming the error is not good here from an administrative point of view, but the user, since he has alternatives, should be able to go to another alternative and continue
                afeList.add(afe);
                setExecutionStatus(alternative, AuthenticationSessionModel.ExecutionStatus.ATTEMPTED);
            }
        }
    } else {
        if (requiredElementsSuccessful) {
            return onFlowExecutionsSuccessful();
        }
    }
    return null;
}
Also used : Response(javax.ws.rs.core.Response) AuthenticationExecutionModel(org.keycloak.models.AuthenticationExecutionModel) ArrayList(java.util.ArrayList)

Example 3 with AuthenticationExecutionModel

use of org.keycloak.models.AuthenticationExecutionModel in project keycloak by keycloak.

the class DefaultAuthenticationFlow method checkAndValidateParentFlow.

/**
 * This method makes sure that the parent flow's corresponding execution is considered successful if its contained
 * executions are successful.
 * The purpose is for when an execution is validated through an action, to make sure its parent flow can be successful
 * when re-evaluation the flow tree. If the flow is successful, we will recursively check it's parent flow as well
 *
 * @param model An execution model.
 * @return flowId of the 1st ancestor flow, which is not yet successfully finished and may require some further processing
 */
private String checkAndValidateParentFlow(AuthenticationExecutionModel model) {
    while (true) {
        AuthenticationExecutionModel parentFlowExecutionModel = processor.getRealm().getAuthenticationExecutionByFlowId(model.getParentFlow());
        if (parentFlowExecutionModel != null) {
            List<AuthenticationExecutionModel> requiredExecutions = new LinkedList<>();
            List<AuthenticationExecutionModel> alternativeExecutions = new LinkedList<>();
            fillListsOfExecutions(processor.getRealm().getAuthenticationExecutionsStream(model.getParentFlow()), requiredExecutions, alternativeExecutions);
            // Note: If we evaluate alternative execution, we will also doublecheck that there are not required elements in same subflow
            if (((model.isRequired() || model.isConditional()) && requiredExecutions.stream().allMatch(processor::isSuccessful)) || (model.isAlternative() && alternativeExecutions.stream().anyMatch(processor::isSuccessful) && requiredExecutions.isEmpty())) {
                logger.debugf("Flow '%s' successfully finished after children executions success", logExecutionAlias(parentFlowExecutionModel));
                setExecutionStatus(parentFlowExecutionModel, AuthenticationSessionModel.ExecutionStatus.SUCCESS);
                // Flow is successfully finished. Recursively check whether it's parent flow is now successful as well
                model = parentFlowExecutionModel;
            } else {
                return model.getParentFlow();
            }
        } else {
            return model.getParentFlow();
        }
    }
}
Also used : AuthenticationExecutionModel(org.keycloak.models.AuthenticationExecutionModel) LinkedList(java.util.LinkedList)

Example 4 with AuthenticationExecutionModel

use of org.keycloak.models.AuthenticationExecutionModel in project keycloak by keycloak.

the class DefaultAuthenticationFlow method processAction.

@Override
public Response processAction(String actionExecution) {
    logger.debugv("processAction: {0}", actionExecution);
    if (actionExecution == null || actionExecution.isEmpty()) {
        throw new AuthenticationFlowException("action is not in current execution", AuthenticationFlowError.INTERNAL_ERROR);
    }
    AuthenticationExecutionModel model = processor.getRealm().getAuthenticationExecutionById(actionExecution);
    if (model == null) {
        throw new AuthenticationFlowException("Execution not found", AuthenticationFlowError.INTERNAL_ERROR);
    }
    if (HttpMethod.POST.equals(processor.getRequest().getHttpMethod())) {
        MultivaluedMap<String, String> inputData = processor.getRequest().getDecodedFormParameters();
        String authExecId = inputData.getFirst(Constants.AUTHENTICATION_EXECUTION);
        // User clicked on "try another way" link
        if (inputData.containsKey("tryAnotherWay")) {
            logger.trace("User clicked on link 'Try Another Way'");
            List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model);
            AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, null, null);
            result.setAuthenticationSelections(selectionOptions);
            return result.form().createSelectAuthenticator();
        }
        // check if the user has switched to a new authentication execution, and if so switch to it.
        if (authExecId != null && !authExecId.isEmpty()) {
            List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model);
            // Check if switch to the requested authentication execution is allowed
            selectionOptions.stream().filter(authSelectionOption -> authExecId.equals(authSelectionOption.getAuthExecId())).findFirst().orElseThrow(() -> new AuthenticationFlowException("Requested authentication execution is not allowed", AuthenticationFlowError.INTERNAL_ERROR));
            model = processor.getRealm().getAuthenticationExecutionById(authExecId);
            Response response = processSingleFlowExecutionModel(model, false);
            if (response == null) {
                return continueAuthenticationAfterSuccessfulAction(model);
            } else
                return response;
        }
    }
    // handle case where execution is a flow - This can happen during user registration for example
    if (model.isAuthenticatorFlow()) {
        logger.debug("execution is flow");
        AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
        Response flowChallenge = authenticationFlow.processAction(actionExecution);
        if (flowChallenge == null) {
            checkAndValidateParentFlow(model);
            return processFlow();
        } else {
            setExecutionStatus(model, AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
            return flowChallenge;
        }
    }
    // handle normal execution case
    AuthenticatorFactory factory = getAuthenticatorFactory(model);
    Authenticator authenticator = createAuthenticator(factory);
    AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, authenticator, executions);
    result.setAuthenticationSelections(createAuthenticationSelectionList(model));
    if (factory instanceof AuthenticationFlowCallbackFactory) {
        AuthenticatorUtil.setAuthCallbacksFactoryIds(processor.getAuthenticationSession(), factory.getId());
    }
    logger.debugv("action: {0}", model.getAuthenticator());
    authenticator.action(result);
    Response response = processResult(result, true);
    if (response == null) {
        return continueAuthenticationAfterSuccessfulAction(model);
    } else
        return response;
}
Also used : AuthenticationExecutionModel(org.keycloak.models.AuthenticationExecutionModel) Response(javax.ws.rs.core.Response) ConditionalAuthenticator(org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticator)

Example 5 with AuthenticationExecutionModel

use of org.keycloak.models.AuthenticationExecutionModel in project keycloak by keycloak.

the class DefaultAuthenticationFlow method continueAuthenticationAfterSuccessfulAction.

/**
 * Called after "actionExecutionModel" execution is finished (Either successful or attempted). Find the next appropriate authentication
 * flow where the authentication should continue and continue with authentication process.
 * The method recursively continues with the parent flow
 * until finally the top flow is processed.
 *
 * @param actionExecutionModel
 * @return Response if some more forms should be displayed during authentication. Null otherwise.
 */
private Response continueAuthenticationAfterSuccessfulAction(AuthenticationExecutionModel actionExecutionModel) {
    processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
    String firstUnfinishedParentFlowId = checkAndValidateParentFlow(actionExecutionModel);
    AuthenticationExecutionModel parentFlowExecution = processor.getRealm().getAuthenticationExecutionByFlowId(firstUnfinishedParentFlowId);
    if (parentFlowExecution == null) {
        // This means that 1st unfinished ancestor flow is the top flow. We can just process it from the start
        return processFlow();
    } else {
        Response response = processSingleFlowExecutionModel(parentFlowExecution, false);
        if (response == null) {
            // the parent flow is now the last action that has been executed, continue with that until the top flow is reached
            return continueAuthenticationAfterSuccessfulAction(parentFlowExecution);
        } else {
            return response;
        }
    }
}
Also used : Response(javax.ws.rs.core.Response) AuthenticationExecutionModel(org.keycloak.models.AuthenticationExecutionModel)

Aggregations

AuthenticationExecutionModel (org.keycloak.models.AuthenticationExecutionModel)51 AuthenticationFlowModel (org.keycloak.models.AuthenticationFlowModel)32 AuthenticatorConfigModel (org.keycloak.models.AuthenticatorConfigModel)11 Path (javax.ws.rs.Path)8 NoCache (org.jboss.resteasy.annotations.cache.NoCache)8 HashMap (java.util.HashMap)7 Response (javax.ws.rs.core.Response)7 RealmModel (org.keycloak.models.RealmModel)7 BadRequestException (javax.ws.rs.BadRequestException)6 NotFoundException (javax.ws.rs.NotFoundException)6 POST (javax.ws.rs.POST)6 ArrayList (java.util.ArrayList)5 LinkedList (java.util.LinkedList)5 Consumes (javax.ws.rs.Consumes)5 Before (org.junit.Before)5 ClientModel (org.keycloak.models.ClientModel)4 List (java.util.List)3 UserModel (org.keycloak.models.UserModel)3 MultivaluedMap (javax.ws.rs.core.MultivaluedMap)2 Logger (org.jboss.logging.Logger)2