package org.apereo.cas.adaptors.duo.web.flow.config;
import org.apereo.cas.adaptors.duo.authn.DuoCredential;
import org.apereo.cas.configuration.model.support.mfa.MultifactorAuthenticationProperties;
import org.apereo.cas.services.MultifactorAuthenticationProvider;
import org.apereo.cas.services.VariegatedMultifactorAuthenticationProvider;
import org.apereo.cas.web.flow.AbstractMultifactorTrustedDeviceWebflowConfigurer;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.apereo.cas.web.flow.DynamicFlowModelBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.webflow.config.FlowDefinitionRegistryBuilder;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.builder.FlowBuilder;
import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import org.springframework.webflow.engine.model.AbstractActionModel;
import org.springframework.webflow.engine.model.AbstractStateModel;
import org.springframework.webflow.engine.model.ActionStateModel;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.engine.model.BindingModel;
import org.springframework.webflow.engine.model.EndStateModel;
import org.springframework.webflow.engine.model.EvaluateModel;
import org.springframework.webflow.engine.model.TransitionModel;
import org.springframework.webflow.engine.model.VarModel;
import org.springframework.webflow.engine.model.ViewStateModel;
import org.springframework.webflow.engine.model.builder.DefaultFlowModelHolder;
import org.springframework.webflow.engine.model.registry.FlowModelHolder;
import java.util.LinkedList;
/**
* This is {@link DuoMultifactorWebflowConfigurer}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public class DuoMultifactorWebflowConfigurer extends AbstractMultifactorTrustedDeviceWebflowConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(DuoMultifactorWebflowConfigurer.class);
private static final String STATE_ID_VIEW_LOGIN_FORM_DUO = "viewLoginFormDuo";
private final VariegatedMultifactorAuthenticationProvider provider;
public DuoMultifactorWebflowConfigurer(final FlowBuilderServices flowBuilderServices, final FlowDefinitionRegistry loginFlowDefinitionRegistry,
final boolean enableDeviceRegistration, final VariegatedMultifactorAuthenticationProvider provider) {
super(flowBuilderServices, loginFlowDefinitionRegistry, enableDeviceRegistration);
this.provider = provider;
}
@Override
protected void doInitialize() throws Exception {
provider.getProviders().forEach(p -> {
final FlowDefinitionRegistry duoFlowRegistry = buildDuoFlowRegistry(p);
applicationContext.getAutowireCapableBeanFactory().initializeBean(duoFlowRegistry, p.getId());
final ConfigurableListableBeanFactory cfg = (ConfigurableListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
cfg.registerSingleton(p.getId(), duoFlowRegistry);
registerMultifactorProviderAuthenticationWebflow(getLoginFlow(), p.getId(), duoFlowRegistry);
});
casProperties.getAuthn().getMfa().getDuo()
.stream()
.filter(MultifactorAuthenticationProperties.Duo::isTrustedDeviceEnabled)
.forEach(duo -> {
final String id = duo.getId();
try {
LOGGER.debug("Activating multifactor trusted authentication for webflow [{}]", id);
final FlowDefinitionRegistry registry = applicationContext.getBean(id, FlowDefinitionRegistry.class);
registerMultifactorTrustedAuthentication(registry);
} catch (final Exception e) {
LOGGER.error("Failed to register multifactor trusted authentication for " + id, e);
}
});
}
private FlowDefinitionRegistry buildDuoFlowRegistry(final MultifactorAuthenticationProvider p) {
final DynamicFlowModelBuilder modelBuilder = new DynamicFlowModelBuilder();
// vars
final LinkedList<VarModel> vars = new LinkedList<>();
vars.add(new VarModel(CasWebflowConstants.VAR_ID_CREDENTIAL, DuoCredential.class.getName()));
modelBuilder.setVars(vars);
// starts
final LinkedList<AbstractActionModel> starts = new LinkedList<>();
starts.add(new EvaluateModel("initialFlowSetupAction"));
modelBuilder.setOnStartActions(starts);
// states
final LinkedList<AbstractStateModel> states = new LinkedList<>();
ActionStateModel actModel = new ActionStateModel(CasWebflowConstants.STATE_ID_INIT_LOGIN_FORM);
LinkedList<AbstractActionModel> actions = new LinkedList<>();
actions.add(new EvaluateModel("initializeLoginAction"));
actModel.setActions(actions);
LinkedList<TransitionModel> trans = new LinkedList<>();
TransitionModel transModel = new TransitionModel();
transModel.setOn(CasWebflowConstants.TRANSITION_ID_SUCCESS);
transModel.setTo("determineDuoRequest");
trans.add(transModel);
actModel.setTransitions(trans);
states.add(actModel);
///////////////
actModel = new ActionStateModel("determineDuoRequest");
actions = new LinkedList<>();
actions.add(new EvaluateModel("checkWebAuthenticationRequestAction"));
actModel.setActions(actions);
trans = new LinkedList<>();
transModel = new TransitionModel();
transModel.setOn(CasWebflowConstants.TRANSITION_ID_YES);
transModel.setTo(STATE_ID_VIEW_LOGIN_FORM_DUO);
trans.add(transModel);
transModel = new TransitionModel();
transModel.setOn(CasWebflowConstants.TRANSITION_ID_NO);
transModel.setTo("doNonWebAuthentication");
trans.add(transModel);
actModel.setTransitions(trans);
states.add(actModel);
///////////////
actModel = new ActionStateModel("doNonWebAuthentication");
actions = new LinkedList<>();
actions.add(new EvaluateModel("duoNonWebAuthenticationAction"));
actModel.setActions(actions);
trans = new LinkedList<>();
transModel = new TransitionModel();
transModel.setOn(CasWebflowConstants.TRANSITION_ID_SUCCESS);
transModel.setTo("finalizeAuthentication");
trans.add(transModel);
actModel.setTransitions(trans);
states.add(actModel);
///////////////
actModel = new ActionStateModel("finalizeAuthentication");
actions = new LinkedList<>();
actions.add(new EvaluateModel("duoAuthenticationWebflowAction"));
actModel.setActions(actions);
trans = new LinkedList<>();
transModel = new TransitionModel();
transModel.setOn(CasWebflowConstants.TRANSITION_ID_SUCCESS);
transModel.setTo(CasWebflowConstants.TRANSITION_ID_SUCCESS);
trans.add(transModel);
actModel.setTransitions(trans);
states.add(actModel);
/////////////////
final ViewStateModel viewState = new ViewStateModel(STATE_ID_VIEW_LOGIN_FORM_DUO);
viewState.setView("casDuoLoginView");
viewState.setModel(CasWebflowConstants.VAR_ID_CREDENTIAL);
final BinderModel bm = new BinderModel();
final LinkedList<BindingModel> bindings = new LinkedList<>();
final BindingModel bme = new BindingModel("signedDuoResponse", null, null);
bindings.add(bme);
bm.setBindings(bindings);
viewState.setBinder(bm);
actions = new LinkedList<>();
actions.add(new EvaluateModel("prepareDuoWebLoginFormAction"));
viewState.setOnEntryActions(actions);
transModel = new TransitionModel();
transModel.setOn(CasWebflowConstants.TRANSITION_ID_SUBMIT);
transModel.setTo(CasWebflowConstants.TRANSITION_ID_REAL_SUBMIT);
transModel.setBind(Boolean.TRUE.toString());
transModel.setValidate(Boolean.FALSE.toString());
trans.add(transModel);
viewState.setTransitions(trans);
states.add(viewState);
/////////////////
actModel = new ActionStateModel(CasWebflowConstants.TRANSITION_ID_REAL_SUBMIT);
actions = new LinkedList<>();
actions.add(new EvaluateModel("duoAuthenticationWebflowAction"));
actModel.setActions(actions);
trans = new LinkedList<>();
transModel = new TransitionModel();
transModel.setOn(CasWebflowConstants.TRANSITION_ID_SUCCESS);
transModel.setTo(CasWebflowConstants.TRANSITION_ID_SUCCESS);
trans.add(transModel);
transModel = new TransitionModel();
transModel.setOn(CasWebflowConstants.TRANSITION_ID_ERROR);
transModel.setTo(CasWebflowConstants.STATE_ID_INIT_LOGIN_FORM);
trans.add(transModel);
actModel.setTransitions(trans);
states.add(actModel);
////////////////////
states.add(new EndStateModel(CasWebflowConstants.TRANSITION_ID_SUCCESS));
////////////////////
modelBuilder.setStates(states);
final FlowModelHolder holder = new DefaultFlowModelHolder(modelBuilder);
final FlowBuilder flowBuilder = new FlowModelFlowBuilder(holder);
final FlowDefinitionRegistryBuilder builder = new FlowDefinitionRegistryBuilder(this.applicationContext, flowBuilderServices);
builder.addFlowBuilder(flowBuilder, p.getId());
return builder.build();
}
}