package org.apereo.cas.adaptors.duo.config;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.adaptors.duo.authn.BasicDuoAuthenticationService;
import org.apereo.cas.adaptors.duo.authn.DefaultDuoMultifactorAuthenticationProvider;
import org.apereo.cas.adaptors.duo.authn.DuoAuthenticationHandler;
import org.apereo.cas.adaptors.duo.web.flow.action.PrepareDuoWebLoginFormAction;
import org.apereo.cas.adaptors.duo.web.flow.config.DuoMultifactorWebflowConfigurer;
import org.apereo.cas.authentication.metadata.AuthenticationContextAttributeMetaDataPopulator;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.AuthenticationMetaDataPopulator;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.mfa.MultifactorAuthenticationProperties;
import org.apereo.cas.services.DefaultMultifactorAuthenticationProviderBypass;
import org.apereo.cas.services.DefaultVariegatedMultifactorAuthenticationProvider;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.services.VariegatedMultifactorAuthenticationProvider;
import org.apereo.cas.util.http.HttpClient;
import org.apereo.cas.web.flow.CasWebflowConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import org.springframework.webflow.execution.Action;
import java.util.List;
/**
* This is {@link DuoSecurityAuthenticationEventExecutionPlanConfiguration}.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
@Configuration("duoSecurityAuthenticationEventExecutionPlanConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class DuoSecurityAuthenticationEventExecutionPlanConfiguration implements AuthenticationEventExecutionPlanConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(DuoSecurityAuthenticationEventExecutionPlanConfiguration.class);
@Autowired
@Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowDefinitionRegistry;
@Autowired
private FlowBuilderServices flowBuilderServices;
@Autowired
@Qualifier("noRedirectHttpClient")
private HttpClient httpClient;
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@ConditionalOnMissingBean(name = "duoPrincipalFactory")
@Bean
public PrincipalFactory duoPrincipalFactory() {
return new DefaultPrincipalFactory();
}
@Bean
@RefreshScope
public VariegatedMultifactorAuthenticationProvider duoMultifactorAuthenticationProvider() {
final DefaultVariegatedMultifactorAuthenticationProvider provider = new DefaultVariegatedMultifactorAuthenticationProvider();
casProperties.getAuthn().getMfa().getDuo()
.stream()
.filter(duo -> StringUtils.isNotBlank(duo.getDuoApiHost())
&& StringUtils.isNotBlank(duo.getDuoIntegrationKey())
&& StringUtils.isNotBlank(duo.getDuoSecretKey())
&& StringUtils.isNotBlank(duo.getDuoApplicationKey()))
.forEach(duo -> {
final BasicDuoAuthenticationService s = new BasicDuoAuthenticationService(duo, httpClient);
final DefaultDuoMultifactorAuthenticationProvider pWeb = new DefaultDuoMultifactorAuthenticationProvider(s);
pWeb.setGlobalFailureMode(casProperties.getAuthn().getMfa().getGlobalFailureMode());
pWeb.setBypassEvaluator(new DefaultMultifactorAuthenticationProviderBypass(duo.getBypass()));
pWeb.setOrder(duo.getRank());
pWeb.setId(duo.getId());
provider.addProvider(pWeb);
});
if (provider.getProviders().isEmpty()) {
throw new IllegalArgumentException("At least one Duo instance must be defined");
}
return provider;
}
@Bean
public Action prepareDuoWebLoginFormAction() {
return new PrepareDuoWebLoginFormAction(duoMultifactorAuthenticationProvider());
}
@Bean
@RefreshScope
public AuthenticationMetaDataPopulator duoAuthenticationMetaDataPopulator() {
final String authenticationContextAttribute = casProperties.getAuthn().getMfa().getAuthenticationContextAttribute();
return new AuthenticationContextAttributeMetaDataPopulator(authenticationContextAttribute, duoAuthenticationHandler(),
duoMultifactorAuthenticationProvider());
}
@RefreshScope
@Bean
public AuthenticationHandler duoAuthenticationHandler() {
final DuoAuthenticationHandler h;
final List<MultifactorAuthenticationProperties.Duo> duos = casProperties.getAuthn().getMfa().getDuo();
if (!duos.isEmpty()) {
final String name = duos.get(0).getName();
if (duos.size() > 1) {
LOGGER.debug("Multiple Duo Security providers are available; Duo authentication handler is named after [{}]", name);
}
h = new DuoAuthenticationHandler(name, servicesManager, duoPrincipalFactory(), duoMultifactorAuthenticationProvider());
} else {
h = new DuoAuthenticationHandler(StringUtils.EMPTY, servicesManager, duoPrincipalFactory(), duoMultifactorAuthenticationProvider());
throw new BeanCreationException("No configuration/settings could be found for Duo Security. Review settings and ensure the correct syntax is used");
}
return h;
}
@ConditionalOnMissingBean(name = "duoMultifactorWebflowConfigurer")
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public CasWebflowConfigurer duoMultifactorWebflowConfigurer() {
final boolean deviceRegistrationEnabled = casProperties.getAuthn().getMfa().getTrusted().isDeviceRegistrationEnabled();
return new DuoMultifactorWebflowConfigurer(flowBuilderServices, loginFlowDefinitionRegistry, deviceRegistrationEnabled,
duoMultifactorAuthenticationProvider());
}
@Override
public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(duoAuthenticationHandler());
plan.registerMetadataPopulator(duoAuthenticationMetaDataPopulator());
}
}