package org.apereo.cas.adaptors.gauth;
import com.warrenstrange.googleauth.IGoogleAuthenticator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apereo.cas.adaptors.gauth.repository.token.GoogleAuthenticatorToken;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.otp.repository.credentials.OneTimeTokenCredentialRepository;
import org.apereo.cas.otp.repository.token.OneTimeTokenRepository;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;
import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
/**
* An authentication handler that uses the token provided
* to authenticator against google authN for MFA.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public class GoogleAuthenticatorAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GoogleAuthenticatorAuthenticationHandler.class);
private final IGoogleAuthenticator googleAuthenticatorInstance;
private final OneTimeTokenRepository tokenRepository;
private final OneTimeTokenCredentialRepository credentialRepository;
public GoogleAuthenticatorAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
final IGoogleAuthenticator googleAuthenticatorInstance, final OneTimeTokenRepository tokenRepository,
final OneTimeTokenCredentialRepository credentialRepository) {
super(name, servicesManager, principalFactory, null);
this.googleAuthenticatorInstance = googleAuthenticatorInstance;
this.tokenRepository = tokenRepository;
this.credentialRepository = credentialRepository;
}
@Override
protected HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException {
final GoogleAuthenticatorTokenCredential tokenCredential = (GoogleAuthenticatorTokenCredential) credential;
if (!NumberUtils.isCreatable(tokenCredential.getToken())) {
throw new PreventedException("Invalid non-numeric OTP format specified.",
new IllegalArgumentException("Invalid token " + tokenCredential.getToken()));
}
final int otp = Integer.parseInt(tokenCredential.getToken());
LOGGER.debug("Received OTP [{}]", otp);
final RequestContext context = RequestContextHolder.getRequestContext();
if (context == null) {
new IllegalArgumentException("No request context could be found to locate an authentication event");
}
final Authentication authentication = WebUtils.getAuthentication(context);
if (authentication == null) {
new IllegalArgumentException("Request context has no reference to an authentication event to locate a principal");
}
final String uid = authentication.getPrincipal().getId();
LOGGER.debug("Received principal id [{}]", uid);
final String secKey = this.credentialRepository.getSecret(uid);
if (StringUtils.isBlank(secKey)) {
throw new AccountNotFoundException(uid + " cannot be found in the registry");
}
if (this.tokenRepository.exists(uid, otp)) {
throw new AccountExpiredException(uid + " cannot reuse OTP " + otp + " as it may be expired/invalid");
}
final boolean isCodeValid = this.googleAuthenticatorInstance.authorize(secKey, otp);
if (isCodeValid) {
this.tokenRepository.store(new GoogleAuthenticatorToken(otp, uid));
return createHandlerResult(tokenCredential, this.principalFactory.createPrincipal(uid), null);
}
throw new FailedLoginException("Failed to authenticate code " + otp);
}
@Override
public boolean supports(final Credential credential) {
return GoogleAuthenticatorTokenCredential.class.isAssignableFrom(credential.getClass());
}
}