package org.apereo.cas.config;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.api.AuthenticationRequestRiskCalculator;
import org.apereo.cas.api.AuthenticationRiskContingencyPlan;
import org.apereo.cas.api.AuthenticationRiskEvaluator;
import org.apereo.cas.api.AuthenticationRiskMitigator;
import org.apereo.cas.api.AuthenticationRiskNotifier;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.core.authentication.RiskBasedAuthenticationProperties;
import org.apereo.cas.configuration.model.support.sms.SmsProperties;
import org.apereo.cas.impl.calcs.DateTimeAuthenticationRequestRiskCalculator;
import org.apereo.cas.impl.calcs.GeoLocationAuthenticationRequestRiskCalculator;
import org.apereo.cas.impl.calcs.IpAddressAuthenticationRequestRiskCalculator;
import org.apereo.cas.impl.calcs.UserAgentAuthenticationRequestRiskCalculator;
import org.apereo.cas.impl.engine.DefaultAuthenticationRiskEvaluator;
import org.apereo.cas.impl.engine.DefaultAuthenticationRiskMitigator;
import org.apereo.cas.impl.notify.AuthenticationRiskEmailNotifier;
import org.apereo.cas.impl.notify.AuthenticationRiskTwilioSmsNotifier;
import org.apereo.cas.impl.plans.BaseAuthenticationRiskContingencyPlan;
import org.apereo.cas.impl.plans.BlockAuthenticationContingencyPlan;
import org.apereo.cas.impl.plans.MultifactorAuthenticationContingencyPlan;
import org.apereo.cas.services.MultifactorAuthenticationProviderSelector;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.support.events.CasEventRepository;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.util.io.CommunicationsManager;
import org.apereo.cas.web.flow.CasWebflowConfigurer;
import org.apereo.cas.web.flow.RiskAwareAuthenticationWebflowConfigurer;
import org.apereo.cas.web.flow.RiskAwareAuthenticationWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.scheduling.annotation.EnableScheduling;
import org.springframework.web.util.CookieGenerator;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import java.util.HashSet;
import java.util.Set;
/**
* This is {@link ElectronicFenceConfiguration}.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
@Configuration("electronicFenceConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
@EnableScheduling
public class ElectronicFenceConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(ElectronicFenceConfiguration.class);
@Autowired
@Qualifier("communicationsManager")
private CommunicationsManager communicationsManager;
@Autowired
@Qualifier("centralAuthenticationService")
private CentralAuthenticationService centralAuthenticationService;
@Autowired
@Qualifier("defaultTicketRegistrySupport")
private TicketRegistrySupport ticketRegistrySupport;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Autowired
@Qualifier("warnCookieGenerator")
private CookieGenerator warnCookieGenerator;
@Autowired
@Qualifier("authenticationServiceSelectionPlan")
private AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies;
@Autowired(required = false)
private FlowBuilderServices flowBuilderServices;
@Autowired(required = false)
@Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowDefinitionRegistry;
@Autowired
@Qualifier("casEventRepository")
private CasEventRepository casEventRepository;
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("multifactorAuthenticationProviderSelector")
private MultifactorAuthenticationProviderSelector selector;
@Autowired
@Qualifier("initialAuthenticationAttemptWebflowEventResolver")
private CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver;
@ConditionalOnMissingBean(name = "authenticationRiskEmailNotifier")
@Bean
@RefreshScope
public AuthenticationRiskNotifier authenticationRiskEmailNotifier() {
return new AuthenticationRiskEmailNotifier(communicationsManager);
}
@ConditionalOnMissingBean(name = "authenticationRiskSmsNotifier")
@Bean
@RefreshScope
public AuthenticationRiskNotifier authenticationRiskSmsNotifier() {
return new AuthenticationRiskTwilioSmsNotifier(communicationsManager);
}
@ConditionalOnMissingBean(name = "riskAwareAuthenticationWebflowEventResolver")
@Bean
@Autowired
@RefreshScope
public CasWebflowEventResolver riskAwareAuthenticationWebflowEventResolver(@Qualifier("defaultAuthenticationSystemSupport")
final AuthenticationSystemSupport authenticationSystemSupport) {
final CasWebflowEventResolver r = new RiskAwareAuthenticationWebflowEventResolver(authenticationSystemSupport, centralAuthenticationService,
servicesManager,
ticketRegistrySupport, warnCookieGenerator,
authenticationRequestServiceSelectionStrategies, selector, authenticationRiskEvaluator(),
authenticationRiskMitigator(), casProperties);
this.initialAuthenticationAttemptWebflowEventResolver.addDelegate(r, 0);
return r;
}
@ConditionalOnMissingBean(name = "blockAuthenticationContingencyPlan")
@Bean
@RefreshScope
public AuthenticationRiskContingencyPlan blockAuthenticationContingencyPlan() {
final BlockAuthenticationContingencyPlan b = new BlockAuthenticationContingencyPlan();
configureContingencyPlan(b);
return b;
}
@ConditionalOnMissingBean(name = "multifactorAuthenticationContingencyPlan")
@Bean
@RefreshScope
public AuthenticationRiskContingencyPlan multifactorAuthenticationContingencyPlan() {
final MultifactorAuthenticationContingencyPlan b = new MultifactorAuthenticationContingencyPlan();
configureContingencyPlan(b);
return b;
}
@ConditionalOnMissingBean(name = "authenticationRiskMitigator")
@Bean
@RefreshScope
public AuthenticationRiskMitigator authenticationRiskMitigator() {
if (casProperties.getAuthn().getAdaptive().getRisk().getResponse().isBlockAttempt()) {
return new DefaultAuthenticationRiskMitigator(blockAuthenticationContingencyPlan());
}
return new DefaultAuthenticationRiskMitigator(multifactorAuthenticationContingencyPlan());
}
@ConditionalOnMissingBean(name = "ipAddressAuthenticationRequestRiskCalculator")
@Bean
@RefreshScope
public AuthenticationRequestRiskCalculator ipAddressAuthenticationRequestRiskCalculator() {
return new IpAddressAuthenticationRequestRiskCalculator(this.casEventRepository);
}
@ConditionalOnMissingBean(name = "userAgentAuthenticationRequestRiskCalculator")
@Bean
@RefreshScope
public AuthenticationRequestRiskCalculator userAgentAuthenticationRequestRiskCalculator() {
return new UserAgentAuthenticationRequestRiskCalculator(this.casEventRepository);
}
@ConditionalOnMissingBean(name = "dateTimeAuthenticationRequestRiskCalculator")
@Bean
@RefreshScope
public AuthenticationRequestRiskCalculator dateTimeAuthenticationRequestRiskCalculator() {
return new DateTimeAuthenticationRequestRiskCalculator(this.casEventRepository,
casProperties.getAuthn().getAdaptive().getRisk().getDateTime().getWindowInHours());
}
@ConditionalOnMissingBean(name = "geoLocationAuthenticationRequestRiskCalculator")
@Bean
@RefreshScope
public AuthenticationRequestRiskCalculator geoLocationAuthenticationRequestRiskCalculator() {
return new GeoLocationAuthenticationRequestRiskCalculator(this.casEventRepository);
}
@ConditionalOnMissingBean(name = "riskAwareAuthenticationWebflowConfigurer")
@Bean
@RefreshScope
public CasWebflowConfigurer riskAwareAuthenticationWebflowConfigurer() {
return new RiskAwareAuthenticationWebflowConfigurer(flowBuilderServices, loginFlowDefinitionRegistry);
}
@ConditionalOnMissingBean(name = "authenticationRiskEvaluator")
@Bean
@RefreshScope
public AuthenticationRiskEvaluator authenticationRiskEvaluator() {
final RiskBasedAuthenticationProperties risk = casProperties.getAuthn().getAdaptive().getRisk();
final Set<AuthenticationRequestRiskCalculator> calculators = new HashSet<>();
if (risk.getIp().isEnabled()) {
calculators.add(ipAddressAuthenticationRequestRiskCalculator());
}
if (risk.getAgent().isEnabled()) {
calculators.add(userAgentAuthenticationRequestRiskCalculator());
}
if (risk.getDateTime().isEnabled()) {
calculators.add(dateTimeAuthenticationRequestRiskCalculator());
}
if (risk.getGeoLocation().isEnabled()) {
calculators.add(geoLocationAuthenticationRequestRiskCalculator());
}
if (calculators.isEmpty()) {
LOGGER.warn("No risk calculators are defined to examine authentication requests");
}
return new DefaultAuthenticationRiskEvaluator(calculators);
}
private void configureContingencyPlan(final BaseAuthenticationRiskContingencyPlan b) {
final RiskBasedAuthenticationProperties.Response.Mail mail =
casProperties.getAuthn().getAdaptive().getRisk().getResponse().getMail();
if (StringUtils.isNotBlank(mail.getText()) && StringUtils.isNotBlank(mail.getFrom()) && StringUtils.isNotBlank(mail.getSubject())) {
b.getNotifiers().add(authenticationRiskEmailNotifier());
}
final SmsProperties sms = casProperties.getAuthn().getAdaptive().getRisk().getResponse().getSms();
if (StringUtils.isNotBlank(sms.getText()) && StringUtils.isNotBlank(sms.getFrom())) {
b.getNotifiers().add(authenticationRiskSmsNotifier());
}
}
}