package org.apereo.cas.config.support.authentication; import com.warrenstrange.googleauth.GoogleAuthenticator; import com.warrenstrange.googleauth.GoogleAuthenticatorConfig; import com.warrenstrange.googleauth.IGoogleAuthenticator; import com.warrenstrange.googleauth.KeyRepresentation; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.adaptors.gauth.GoogleAuthenticatorAuthenticationHandler; import org.apereo.cas.adaptors.gauth.GoogleAuthenticatorMultifactorAuthenticationProvider; import org.apereo.cas.adaptors.gauth.repository.credentials.InMemoryGoogleAuthenticatorTokenCredentialRepository; import org.apereo.cas.adaptors.gauth.repository.credentials.JsonGoogleAuthenticatorTokenCredentialRepository; import org.apereo.cas.adaptors.gauth.repository.credentials.RestGoogleAuthenticatorTokenCredentialRepository; import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer; import org.apereo.cas.authentication.AuthenticationHandler; import org.apereo.cas.authentication.AuthenticationMetaDataPopulator; import org.apereo.cas.authentication.metadata.AuthenticationContextAttributeMetaDataPopulator; import org.apereo.cas.authentication.principal.DefaultPrincipalFactory; import org.apereo.cas.authentication.principal.PrincipalFactory; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.model.support.mfa.MultifactorAuthenticationProperties; import org.apereo.cas.otp.repository.credentials.OneTimeTokenCredentialRepository; import org.apereo.cas.otp.repository.token.OneTimeTokenRepository; import org.apereo.cas.otp.repository.token.OneTimeTokenRepositoryCleaner; import org.apereo.cas.otp.web.flow.OneTimeTokenAccountCheckRegistrationAction; import org.apereo.cas.otp.web.flow.OneTimeTokenAccountSaveRegistrationAction; import org.apereo.cas.services.DefaultMultifactorAuthenticationProviderBypass; import org.apereo.cas.services.MultifactorAuthenticationProvider; import org.apereo.cas.services.MultifactorAuthenticationProviderBypass; import org.apereo.cas.services.ServicesManager; 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.autoconfigure.condition.ConditionalOnProperty; 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.context.annotation.Lazy; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.client.RestTemplate; import org.springframework.webflow.execution.Action; import java.util.concurrent.TimeUnit; /** * This is {@link GoogleAuthenticatorAuthenticationEventExecutionPlanConfiguration}. * * @author Misagh Moayyed * @since 5.1.0 */ @Configuration("googleAuthenticatorAuthenticationEventExecutionPlanConfiguration") @EnableConfigurationProperties(CasConfigurationProperties.class) public class GoogleAuthenticatorAuthenticationEventExecutionPlanConfiguration implements AuthenticationEventExecutionPlanConfigurer { @Lazy @Autowired @Qualifier("googleAuthenticatorAccountRegistry") private OneTimeTokenCredentialRepository googleAuthenticatorAccountRegistry; @Lazy @Autowired @Qualifier("oneTimeTokenAuthenticatorTokenRepository") private OneTimeTokenRepository oneTimeTokenAuthenticatorTokenRepository; @Autowired private CasConfigurationProperties casProperties; @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; @Bean public IGoogleAuthenticator googleAuthenticatorInstance() { final MultifactorAuthenticationProperties.GAuth gauth = casProperties.getAuthn().getMfa().getGauth(); final GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder bldr = new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder(); bldr.setCodeDigits(gauth.getCodeDigits()); bldr.setTimeStepSizeInMillis(TimeUnit.SECONDS.toMillis(gauth.getTimeStepSize())); bldr.setWindowSize(gauth.getWindowSize()); bldr.setKeyRepresentation(KeyRepresentation.BASE32); return new GoogleAuthenticator(bldr.build()); } @ConditionalOnMissingBean(name = "googleAuthenticatorAuthenticationHandler") @Bean @RefreshScope public AuthenticationHandler googleAuthenticatorAuthenticationHandler() { return new GoogleAuthenticatorAuthenticationHandler(casProperties.getAuthn().getMfa().getGauth().getName(), servicesManager, googlePrincipalFactory(), googleAuthenticatorInstance(), oneTimeTokenAuthenticatorTokenRepository, googleAuthenticatorAccountRegistry); } @Bean @RefreshScope public MultifactorAuthenticationProviderBypass googleBypassEvaluator() { return new DefaultMultifactorAuthenticationProviderBypass(casProperties.getAuthn().getMfa().getGauth().getBypass()); } @Bean @RefreshScope public MultifactorAuthenticationProvider googleAuthenticatorAuthenticationProvider() { final MultifactorAuthenticationProperties.GAuth gauth = casProperties.getAuthn().getMfa().getGauth(); final GoogleAuthenticatorMultifactorAuthenticationProvider p = new GoogleAuthenticatorMultifactorAuthenticationProvider(); p.setBypassEvaluator(googleBypassEvaluator()); p.setGlobalFailureMode(casProperties.getAuthn().getMfa().getGlobalFailureMode()); p.setOrder(gauth.getRank()); p.setId(gauth.getId()); return p; } @Bean @RefreshScope public AuthenticationMetaDataPopulator googleAuthenticatorAuthenticationMetaDataPopulator() { return new AuthenticationContextAttributeMetaDataPopulator( casProperties.getAuthn().getMfa().getAuthenticationContextAttribute(), googleAuthenticatorAuthenticationHandler(), googleAuthenticatorAuthenticationProvider() ); } @Bean @RefreshScope public Action googleAccountRegistrationAction() { final MultifactorAuthenticationProperties.GAuth gauth = casProperties.getAuthn().getMfa().getGauth(); return new OneTimeTokenAccountCheckRegistrationAction(googleAuthenticatorAccountRegistry, gauth.getLabel(), gauth.getIssuer()); } @ConditionalOnProperty(prefix = "cas.authn.mfa.gauth.cleaner", name = "enabled", havingValue = "true", matchIfMissing = true) @Bean @Autowired public OneTimeTokenRepositoryCleaner googleAuthenticatorTokenRepositoryCleaner(@Qualifier("oneTimeTokenAuthenticatorTokenRepository") final OneTimeTokenRepository oneTimeTokenAuthenticatorTokenRepository) { return new GoogleAuthenticatorOneTimeTokenRepositoryCleaner(oneTimeTokenAuthenticatorTokenRepository); } @ConditionalOnMissingBean(name = "googleAuthenticatorAccountRegistry") @Bean @RefreshScope public OneTimeTokenCredentialRepository googleAuthenticatorAccountRegistry() { final MultifactorAuthenticationProperties.GAuth gauth = casProperties.getAuthn().getMfa().getGauth(); if (gauth.getJson().getConfig().getLocation() != null) { return new JsonGoogleAuthenticatorTokenCredentialRepository(gauth.getJson().getConfig().getLocation(), googleAuthenticatorInstance()); } if (StringUtils.isNotBlank(gauth.getRest().getEndpointUrl())) { return new RestGoogleAuthenticatorTokenCredentialRepository(googleAuthenticatorInstance(), new RestTemplate(), gauth); } return new InMemoryGoogleAuthenticatorTokenCredentialRepository(googleAuthenticatorInstance()); } @Bean @RefreshScope public Action googleSaveAccountRegistrationAction() { return new OneTimeTokenAccountSaveRegistrationAction(this.googleAuthenticatorAccountRegistry); } @ConditionalOnMissingBean(name = "googlePrincipalFactory") @Bean public PrincipalFactory googlePrincipalFactory() { return new DefaultPrincipalFactory(); } @Override public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) { if (StringUtils.isNotBlank(casProperties.getAuthn().getMfa().getGauth().getIssuer())) { plan.registerAuthenticationHandler(googleAuthenticatorAuthenticationHandler()); plan.registerMetadataPopulator(googleAuthenticatorAuthenticationMetaDataPopulator()); } } /** * The type Google authenticator one time token repository cleaner. */ public class GoogleAuthenticatorOneTimeTokenRepositoryCleaner extends OneTimeTokenRepositoryCleaner { public GoogleAuthenticatorOneTimeTokenRepositoryCleaner(final OneTimeTokenRepository tokenRepository) { super(tokenRepository); } @Scheduled(initialDelayString = "${cas.authn.mfa.gauth.cleaner.startDelay:PT30S}", fixedDelayString = "${cas.authn.mfa.gauth.cleaner.repeatInterval:PT35S}") @Override public void clean() { super.clean(); } } }