package org.apereo.cas.adaptors.duo.authn;
import com.duosecurity.client.Http;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Throwables;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apereo.cas.adaptors.duo.DuoUserAccountAuthStatus;
import org.apereo.cas.configuration.model.support.mfa.MultifactorAuthenticationProperties;
import org.apereo.cas.util.http.HttpClient;
import org.apereo.cas.util.http.HttpMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
/**
* This is {@link BaseDuoAuthenticationService}.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
public abstract class BaseDuoAuthenticationService implements DuoAuthenticationService {
private static final int AUTH_API_VERSION = 2;
private static final ObjectMapper MAPPER = new ObjectMapper().findAndRegisterModules();
private static final String RESULT_KEY_RESPONSE = "response";
private static final String RESULT_KEY_STAT = "stat";
private static final long serialVersionUID = -8044100706027708789L;
private static final Logger LOGGER = LoggerFactory.getLogger(BaseDuoAuthenticationService.class);
/**
* Duo Properties.
*/
protected final MultifactorAuthenticationProperties.Duo duoProperties;
private final transient HttpClient httpClient;
/**
* Creates the duo authentication service.
*
* @param duoProperties the duo properties
* @param httpClient the http client
*/
public BaseDuoAuthenticationService(final MultifactorAuthenticationProperties.Duo duoProperties, final HttpClient httpClient) {
this.duoProperties = duoProperties;
this.httpClient = httpClient;
}
@Override
public boolean ping() {
try {
final String url = buildUrlHttpScheme(getApiHost().concat("/rest/v1/ping"));
LOGGER.debug("Contacting Duo @ [{}]", url);
final HttpMessage msg = this.httpClient.sendMessageToEndPoint(new URL(url));
if (msg != null) {
final String response = URLDecoder.decode(msg.getMessage(), StandardCharsets.UTF_8.name());
LOGGER.debug("Received Duo ping response [{}]", response);
final JsonNode result = MAPPER.readTree(response);
if (result.has(RESULT_KEY_RESPONSE) && result.has(RESULT_KEY_STAT)
&& result.get(RESULT_KEY_RESPONSE).asText().equalsIgnoreCase("pong")
&& result.get(RESULT_KEY_STAT).asText().equalsIgnoreCase("OK")) {
return true;
}
LOGGER.warn("Could not reach/ping Duo. Response returned is [{}]", result);
}
} catch (final Exception e) {
LOGGER.warn("Pinging Duo has failed with error: [{}]", e.getMessage(), e);
}
return false;
}
@Override
public String getApiHost() {
return duoProperties.getDuoApiHost();
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj.getClass() != getClass()) {
return false;
}
final BaseDuoAuthenticationService rhs = (BaseDuoAuthenticationService) obj;
return new EqualsBuilder()
.append(this.duoProperties.getDuoApiHost(), rhs.duoProperties.getDuoApiHost())
.append(this.duoProperties.getDuoApplicationKey(), rhs.duoProperties.getDuoApplicationKey())
.append(this.duoProperties.getDuoIntegrationKey(), rhs.duoProperties.getDuoIntegrationKey())
.append(this.duoProperties.getDuoSecretKey(), rhs.duoProperties.getDuoSecretKey())
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(this.duoProperties.getDuoApiHost())
.append(this.duoProperties.getDuoApplicationKey())
.append(this.duoProperties.getDuoIntegrationKey())
.append(this.duoProperties.getDuoSecretKey())
.toHashCode();
}
@Override
public DuoUserAccountAuthStatus getDuoUserAccountAuthStatus(final String username) {
try {
final Http userRequest = buildHttpPostUserPreAuthRequest(username);
signHttpUserPreAuthRequest(userRequest);
LOGGER.debug("Contacting Duo to inquire about username [{}]", username);
final String userResponse = userRequest.executeHttpRequest().body().string();
final String jsonResponse = URLDecoder.decode(userResponse, StandardCharsets.UTF_8.name());
LOGGER.debug("Received Duo admin response [{}]", jsonResponse);
final JsonNode result = MAPPER.readTree(jsonResponse);
if (result.has(RESULT_KEY_RESPONSE) && result.has(RESULT_KEY_STAT)
&& result.get(RESULT_KEY_STAT).asText().equalsIgnoreCase("OK")) {
final JsonNode response = result.get(RESULT_KEY_RESPONSE);
final String authResult = response.get("result").asText().toUpperCase();
return DuoUserAccountAuthStatus.valueOf(authResult);
}
} catch (final Exception e) {
LOGGER.warn("Reaching Duo has failed with error: [{}]", e.getMessage(), e);
}
return DuoUserAccountAuthStatus.AUTH;
}
private static String buildUrlHttpScheme(final String url) {
if (!url.startsWith("http")) {
return "https://" + url;
}
return url;
}
/**
* Build http post auth request http.
*
* @return the http
*/
protected Http buildHttpPostAuthRequest() {
return new Http(HttpMethod.POST.name(),
duoProperties.getDuoApiHost(),
String.format("/auth/v%s/auth", AUTH_API_VERSION));
}
/**
* Build http post get user auth request.
*
* @param username the username
* @return the http
*/
protected Http buildHttpPostUserPreAuthRequest(final String username) {
final Http usersRequest = new Http(HttpMethod.POST.name(),
duoProperties.getDuoApiHost(),
String.format("/auth/v%s/preauth", AUTH_API_VERSION));
usersRequest.addParam("username", username);
return usersRequest;
}
/**
* Sign http request.
*
* @param request the request
* @param id the id
* @return the http
*/
protected Http signHttpAuthRequest(final Http request, final String id) {
try {
request.addParam("username", id);
request.addParam("factor", "auto");
request.addParam("device", "auto");
request.signRequest(
duoProperties.getDuoIntegrationKey(),
duoProperties.getDuoSecretKey());
return request;
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Sign http users request http.
*
* @param request the request
* @return the http
*/
protected Http signHttpUserPreAuthRequest(final Http request) {
try {
request.signRequest(
duoProperties.getDuoIntegrationKey(),
duoProperties.getDuoSecretKey());
return request;
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
}