package org.apereo.cas.adaptors.duo.authn;
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.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.MultifactorAuthenticationProvider;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.services.VariegatedMultifactorAuthenticationProvider;
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.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
/**
* Authenticate CAS credentials against Duo Security.
*
* @author Misagh Moayyed
* @author Dmitriy Kopylenko
* @since 4.2
*/
public class DuoAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DuoAuthenticationHandler.class);
private final VariegatedMultifactorAuthenticationProvider provider;
public DuoAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
final VariegatedMultifactorAuthenticationProvider provider) {
super(name, servicesManager, principalFactory, null);
this.provider = provider;
}
/**
* Do an out of band request using the DuoWeb api (encapsulated in DuoAuthenticationService)
* to the hosted duo service. If it is successful
* it will return a String containing the username of the successfully authenticated user, but if not - will
* return a blank String or null.
*
* @param credential Credential to authenticate.
* @return the result of this handler
* @throws GeneralSecurityException general security exception for errors
* @throws PreventedException authentication failed exception
*/
@Override
protected HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException {
if (credential instanceof DuoDirectCredential) {
LOGGER.debug("Attempting to directly authenticate credential against Duo");
return authenticateDuoApiCredential(credential);
}
return authenticateDuoCredential(credential);
}
private HandlerResult authenticateDuoApiCredential(final Credential credential) throws FailedLoginException {
try {
final DuoAuthenticationService duoAuthenticationService = getDuoAuthenticationService();
final DuoDirectCredential c = DuoDirectCredential.class.cast(credential);
if (duoAuthenticationService.authenticate(c).getKey()) {
final Principal principal = c.getAuthentication().getPrincipal();
LOGGER.debug("Duo has successfully authenticated [{}]", principal.getId());
return createHandlerResult(credential, principal, new ArrayList<>());
}
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
throw new FailedLoginException("Duo authentication has failed");
}
private HandlerResult authenticateDuoCredential(final Credential credential) throws FailedLoginException {
try {
final DuoCredential duoCredential = (DuoCredential) credential;
if (!duoCredential.isValid()) {
throw new GeneralSecurityException("Duo credential validation failed. Ensure a username "
+ " and the signed Duo response is configured and passed. Credential received: " + duoCredential);
}
final DuoAuthenticationService duoAuthenticationService = getDuoAuthenticationService();
final String duoVerifyResponse = duoAuthenticationService.authenticate(duoCredential).getValue();
LOGGER.debug("Response from Duo verify: [{}]", duoVerifyResponse);
final String primaryCredentialsUsername = duoCredential.getUsername();
final boolean isGoodAuthentication = duoVerifyResponse.equals(primaryCredentialsUsername);
if (isGoodAuthentication) {
LOGGER.info("Successful Duo authentication for [{}]", primaryCredentialsUsername);
final Principal principal = this.principalFactory.createPrincipal(duoVerifyResponse);
return createHandlerResult(credential, principal, new ArrayList<>());
}
throw new FailedLoginException("Duo authentication username "
+ primaryCredentialsUsername + " does not match Duo response: " + duoVerifyResponse);
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
throw new FailedLoginException(e.getMessage());
}
}
private DuoAuthenticationService getDuoAuthenticationService() {
final RequestContext requestContext = RequestContextHolder.getRequestContext();
if (requestContext == null) {
throw new IllegalArgumentException("No request context is held to locate the Duo authentication service");
}
final Collection<MultifactorAuthenticationProvider> col = WebUtils.getResolvedMultifactorAuthenticationProviders(requestContext);
if (col.isEmpty()) {
throw new IllegalArgumentException("No multifactor providers are found in the current request context");
}
final MultifactorAuthenticationProvider pr = col.iterator().next();
return provider.findProvider(pr.getId(), DuoMultifactorAuthenticationProvider.class).getDuoAuthenticationService();
}
@Override
public boolean supports(final Credential credential) {
return DuoCredential.class.isAssignableFrom(credential.getClass())
|| credential instanceof DuoDirectCredential;
}
}