public void verifyAuthenticatedUser()

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")
                )
            ))
        );

    }