in extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java [110:274]
public void verifyAuthenticatedUser(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
// Pull the original HTTP request used to authenticate
Credentials credentials = authenticatedUser.getCredentials();
IPAddress clientAddr = new IPAddressString(credentials.getRemoteAddress()).getAddress();
// Ignore anonymous users
String username = authenticatedUser.getIdentifier();
if (username == null || username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
return;
// Pull address lists to check from configuration. Note that the enforce
// list will override the bypass list, which means that, if the client
// address happens to be in both lists, Duo MFA will be enforced.
List<IPAddress> bypassAddresses = confService.getBypassHosts();
List<IPAddress> enforceAddresses = confService.getEnforceHosts();
// Check if the bypass list contains the client address, and set the
// enforce flag to the opposite.
boolean enforceHost = !(IPAddressListProperty.addressListContains(bypassAddresses, clientAddr));
// Only continue processing if the list is not empty
if (!enforceAddresses.isEmpty()) {
// If client address is not available or invalid, MFA will
// be enforced.
if (clientAddr == null || !clientAddr.isIPAddress())
enforceHost = true;
// Check the enforce list for the client address and set enforcement flag.
else
enforceHost = IPAddressListProperty.addressListContains(enforceAddresses, clientAddr);
}
// If the enforce flag is not true, bypass Duo MFA.
if (!enforceHost)
return;
// Obtain a Duo client for redirecting the user to the Duo service and
// verifying any received authentication code
Client duoClient;
try {
duoClient = new Client.Builder(
confService.getClientId(),
confService.getClientSecret(),
confService.getAPIHostname(),
confService.getRedirectUri().toString())
.build();
}
catch (DuoException e) {
throw new GuacamoleServerException("Client for communicating with "
+ "the Duo authentication service could not be created.", e);
}
// Verify that the Duo service is healthy and available
try {
duoClient.healthCheck();
}
catch (DuoException e) {
throw new GuacamoleServerException("Duo authentication service is "
+ "not currently available (failed health check).", e);
}
// Retrieve signed Duo authentication code and session state from the
// request (these will be absent if this is an initial authentication
// attempt and not a redirect back from Duo)
String duoCode = credentials.getParameter(DUO_CODE_PARAMETER_NAME);
String duoState = credentials.getParameter(DUO_STATE_PARAMETER_NAME);
// Redirect to Duo to obtain an authentication code if that redirect
// has not yet occurred
if (duoCode != null && duoState != null) {
// Validate that the user has successfully verified their identify with
// the Duo service
try {
// Note unexpected behavior (Duo is expected to always return
// a token)
Token token = duoClient.exchangeAuthorizationCodeFor2FAResult(duoCode, username);
if (token == null) {
logger.warn("Duo did not return an authentication result "
+ "at all for the authentication attempt by user "
+ "\"{}\". This is unexpected behavior and may be "
+ "a bug in the Duo service or the Duo SDK. "
+ "Guacamole will attempt to automatically work "
+ "around the issue by making a fresh Duo "
+ "authentication request.", username);
}
// Warn if Duo explicitly denies authentication
else if (token.getAuth_result() == null || !DUO_TOKEN_SUCCESS_VALUE.equals(token.getAuth_result().getStatus())) {
logger.warn("Duo did not return an explicitly successful "
+ "authentication result for the authentication "
+ "attempt by user \"{}\". The user will now be "
+ "redirected back to the Duo service to reattempt"
+ "authentication.", username);
}
// Allow user to continue authenticating with Guacamole only if
// Duo has validated their identity
else
return;
}
catch (DuoException e) {
logger.debug("The Duo client failed internally while "
+ "attempting to validate the identity of user "
+ "\"{}\". This is commonly caused by stale query "
+ "parameters from an older Duo request remaining "
+ "present in the Guacamole URL. The user will now be "
+ "redirected back to the Duo service to reattempt "
+ "authentication.", e);
}
}
// Store received credentials for later retrieval leveraging Duo's
// opaque session state identifier (we need to maintain these
// credentials so that things like the GUAC_USERNAME and
// GUAC_PASSWORD tokens continue to work as expected despite the
// redirect to/from the external Duo service)
duoState = duoClient.generateState();
long expiresAfter = TimeUnit.MINUTES.toMillis(confService.getAuthenticationTimeout());
sessionManager.defer(new DuoAuthenticationSession(credentials, expiresAfter), duoState);
// Obtain authentication URL from Duo client
String duoAuthUrlString;
try {
duoAuthUrlString = duoClient.createAuthUrl(username, duoState);
}
catch (DuoException e) {
throw new GuacamoleServerException("Duo client failed to "
+ "generate the authentication URL necessary to "
+ "redirect the authenticating user to the Duo "
+ "service.", e);
}
// Parse and validate URL obtained from Duo client
URI duoAuthUrl;
try {
duoAuthUrl = new URI(duoAuthUrlString);
}
catch (URISyntaxException e) {
throw new GuacamoleServerException("Authentication URL "
+ "generated by the Duo client is not actually a "
+ "valid URL and cannot be used to redirect the "
+ "authenticating user to the Duo service.", e);
}
// Request that user be redirected to the Duo service to obtain
// a Duo authentication code
throw new TranslatableGuacamoleInsufficientCredentialsException(
"Verification using Duo is required before authentication "
+ "can continue.", "LOGIN.INFO_DUO_AUTH_REQUIRED",
new CredentialsInfo(Collections.singletonList(
new RedirectField(
DUO_CODE_PARAMETER_NAME, duoAuthUrl,
new TranslatableMessage("LOGIN.INFO_DUO_REDIRECT_PENDING")
)
))
);
}