From 8c185ea6d6ce9eab79537a1b34b431f103947751 Mon Sep 17 00:00:00 2001 From: Alex Leitner Date: Thu, 4 Apr 2024 01:32:48 +0000 Subject: [PATCH] GUACAMOLE-1289: Add new translations and guacamole properties. --- .../auth/duo/UserVerificationService.java | 108 +++++++++--------- .../auth/duo/conf/ConfigurationService.java | 32 +++--- .../src/main/resources/translations/en.json | 3 +- .../src/main/resources/translations/ja.json | 3 +- guacamole-docker/bin/start.sh | 18 +-- ...amoleInsufficientCredentialsException.java | 70 ++++++------ .../rest/auth/AuthenticationService.java | 26 ++--- .../auth/ResumableAuthenticationState.java | 2 +- 8 files changed, 134 insertions(+), 128 deletions(-) diff --git a/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java b/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java index efd8a43572..26ab71221e 100644 --- a/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java +++ b/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java @@ -102,60 +102,60 @@ public void verifyAuthenticatedUser(AuthenticatedUser authenticatedUser) try { - String redirectUrl = confService.getRedirectUrl().toString(); - - String builtUrl = UriComponentsBuilder - .fromUriString(redirectUrl) - .queryParam(Credentials.RESUME_QUERY, DuoAuthenticationProvider.PROVIDER_IDENTIFER) - .build() - .toUriString(); - - // Set up the Duo Client - Client duoClient = new Client.Builder( - confService.getClientId(), - confService.getClientSecret(), - confService.getAPIHostname(), - builtUrl) - .build(); - - duoClient.healthCheck(); - - // Retrieve signed Duo Code and State from the request - String duoCode = request.getParameter(DUO_CODE_PARAMETER_NAME); - String duoState = request.getParameter(DUO_STATE_PARAMETER_NAME); - - // If no code or state is received, assume Duo MFA redirect has not occured and do it. - if (duoCode == null || duoState == null) { - - // Get a new session state from the Duo client - duoState = duoClient.generateState(); - long expirationTimestamp = System.currentTimeMillis() + (confService.getAuthTimeout() * 1000L); - - // Request additional credentials - 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, - new URI(duoClient.createAuthUrl(username, duoState)), - new TranslatableMessage("LOGIN.INFO_DUO_REDIRECT_PENDING") - ) - )), - duoState, DuoAuthenticationProvider.PROVIDER_IDENTIFER, - DUO_STATE_PARAMETER_NAME, expirationTimestamp - ); - - } - - // Get the token from the DuoClient using the code and username, and check status - Token token = duoClient.exchangeAuthorizationCodeFor2FAResult(duoCode, username); - if (token == null - || token.getAuth_result() == null - || !DUO_TOKEN_SUCCESS_VALUE.equals(token.getAuth_result().getStatus())) - throw new TranslatableGuacamoleClientException("Provided Duo " - + "validation code is incorrect.", - "LOGIN.INFO_DUO_VALIDATION_CODE_INCORRECT"); + String redirectUrl = confService.getRedirectUri().toString(); + + String builtUrl = UriComponentsBuilder + .fromUriString(redirectUrl) + .queryParam(Credentials.RESUME_QUERY, DuoAuthenticationProvider.PROVIDER_IDENTIFER) + .build() + .toUriString(); + + // Set up the Duo Client + Client duoClient = new Client.Builder( + confService.getClientId(), + confService.getClientSecret(), + confService.getAPIHostname(), + builtUrl) + .build(); + + duoClient.healthCheck(); + + // Retrieve signed Duo Code and State from the request + String duoCode = request.getParameter(DUO_CODE_PARAMETER_NAME); + String duoState = request.getParameter(DUO_STATE_PARAMETER_NAME); + + // If no code or state is received, assume Duo MFA redirect has not occured and do it + if (duoCode == null || duoState == null) { + + // Get a new session state from the Duo client + duoState = duoClient.generateState(); + long expirationTimestamp = System.currentTimeMillis() + (confService.getAuthTimeout() * 1000L); + + // Request additional credentials + 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, + new URI(duoClient.createAuthUrl(username, duoState)), + new TranslatableMessage("LOGIN.INFO_DUO_REDIRECT_PENDING") + ) + )), + duoState, DuoAuthenticationProvider.PROVIDER_IDENTIFER, + DUO_STATE_PARAMETER_NAME, expirationTimestamp + ); + + } + + // Get the token from the DuoClient using the code and username, and check status + Token token = duoClient.exchangeAuthorizationCodeFor2FAResult(duoCode, username); + if (token == null + || token.getAuth_result() == null + || !DUO_TOKEN_SUCCESS_VALUE.equals(token.getAuth_result().getStatus())) + throw new TranslatableGuacamoleClientException("Provided Duo " + + "validation code is incorrect.", + "LOGIN.INFO_DUO_VALIDATION_CODE_INCORRECT"); } catch (DuoException e) { throw new GuacamoleServerException("Duo Client error.", e); diff --git a/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/conf/ConfigurationService.java b/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/conf/ConfigurationService.java index 212b4a6182..f6bed8073e 100644 --- a/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/conf/ConfigurationService.java @@ -55,8 +55,8 @@ public class ConfigurationService { }; /** - * The property within guacamole.properties which defines the integration - * key received from Duo for verifying Guacamole users. This value MUST be + * The property within guacamole.properties which defines the client id + * received from Duo for verifying Guacamole users. This value MUST be * exactly 20 characters. */ private static final StringGuacamoleProperty DUO_CLIENT_ID = @@ -79,17 +79,17 @@ public class ConfigurationService { public String getName() { return "duo-client-secret"; } }; - + /** - * The property within guacamole.properties which defines the redirect URL + * The property within guacamole.properties which defines the redirect URI * that Duo will call after the second factor has been completed. This - * should be the URL used to access Guacamole. + * should be the URI used to access Guacamole. */ - private static final URIGuacamoleProperty DUO_REDIRECT_URL = + private static final URIGuacamoleProperty DUO_REDIRECT_URI = new URIGuacamoleProperty() { - + @Override - public String getName() { return "duo-redirect-url"; } + public String getName() { return "duo-redirect-uri"; } }; @@ -140,8 +140,8 @@ public String getClientId() throws GuacamoleException { } /** - * Returns the client secert received from Duo for verifying Guacamole users, - * as defined in guacamole.properties by the "duo-client-secert" property. + * Returns the client secret received from Duo for verifying Guacamole users, + * as defined in guacamole.properties by the "duo-client-secret" property. * This value MUST be exactly 20 characters. * * @return @@ -153,11 +153,11 @@ public String getClientId() throws GuacamoleException { public String getClientSecret() throws GuacamoleException { return environment.getRequiredProperty(DUO_CLIENT_SECRET); } - + /** - * Return the callback URL that will be called by Duo after authentication - * with Duo has been completed. This should be the URL to return the user - * to the Guacamole interface, and will be a full URL. + * Return the callback URI that will be called by Duo after authentication + * with Duo has been completed. This should be the URI to return the user + * to the Guacamole interface, and will be a full URI. * * @return * The URL for Duo to use to callback to the Guacamole interface after @@ -167,8 +167,8 @@ public String getClientSecret() throws GuacamoleException { * If guacamole.properties cannot be read, or if the property is not * defined. */ - public URI getRedirectUrl() throws GuacamoleException { - return environment.getRequiredProperty(DUO_REDIRECT_URL); + public URI getRedirectUri() throws GuacamoleException { + return environment.getRequiredProperty(DUO_REDIRECT_URI); } /** diff --git a/extensions/guacamole-auth-duo/src/main/resources/translations/en.json b/extensions/guacamole-auth-duo/src/main/resources/translations/en.json index 8682cba35a..877f538b69 100644 --- a/extensions/guacamole-auth-duo/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-duo/src/main/resources/translations/en.json @@ -7,7 +7,8 @@ "LOGIN" : { "FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "", "INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo validation code incorrect.", - "INFO_DUO_AUTH_REQUIRED" : "Please authenticate with Duo to continue." + "INFO_DUO_AUTH_REQUIRED" : "Please authenticate with Duo to continue.", + "INFO_DUO_REDIRECT_PENDING" : "Please wait, redirecting to Duo..." } } diff --git a/extensions/guacamole-auth-duo/src/main/resources/translations/ja.json b/extensions/guacamole-auth-duo/src/main/resources/translations/ja.json index 37ddde2343..d3acef824f 100644 --- a/extensions/guacamole-auth-duo/src/main/resources/translations/ja.json +++ b/extensions/guacamole-auth-duo/src/main/resources/translations/ja.json @@ -2,7 +2,8 @@ "LOGIN" : { "INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duoの認証コードが間違っています。", - "INFO_DUO_AUTH_REQUIRED" : "Duoで認証してください。" + "INFO_DUO_AUTH_REQUIRED" : "Duoで認証してください。", + "INFO_DUO_REDIRECT_PENDING" : "Duoへリダイレクトしています。" } } diff --git a/guacamole-docker/bin/start.sh b/guacamole-docker/bin/start.sh index 02687d8615..732c48f0ee 100755 --- a/guacamole-docker/bin/start.sh +++ b/guacamole-docker/bin/start.sh @@ -850,9 +850,10 @@ associate_totp() { ## associate_duo() { # Verify required parameters are present - if [ -z "$DUO_INTEGRATION_KEY" ] || \ - [ -z "$DUO_SECRET_KEY" ] || \ - [ ${#DUO_APPLICATION_KEY} -lt 40 ] + if [ -z "$DUO_CLIENT_ID" ] || \ + [ -z "$DUO_CLIENT_SECRET" ] || \ + [ ${#DUO_APPLICATION_KEY} -lt 40 ] || \ + [-z "$DUO_REDIRECT_URI"] then cat < getUserContexts(GuacamoleSession existingSess * * @return * Resumed credentials if a valid resumable state is found; otherwise, - * returns {@code null}. + * returns null. */ private Credentials resumeAuthentication(Credentials credentials) { @@ -373,11 +373,11 @@ private Credentials resumeAuthentication(Credentials credentials) { // Retrieve signed State from the request HttpServletRequest request = credentials.getRequest(); - // Retrieve the provider id from the query parameters. + // Retrieve the provider id from the query parameters String resumableProviderId = request.getParameter(Credentials.RESUME_QUERY); - // Check if a provider id is set. + // Check if a provider id is set if (resumableProviderId == null || resumableProviderId.isEmpty()) { - // return if a provider id is not set. + // Return if a provider id is not set return null; } @@ -387,34 +387,34 @@ private Credentials resumeAuthentication(Credentials credentials) { Map.Entry entry = iterator.next(); ResumableAuthenticationState resumableState = entry.getValue(); - // Check if the provider ID from the request matches the one in the map entry. + // Check if the provider ID from the request matches the one in the map entry boolean providerMatches = resumableProviderId.equals(resumableState.getProviderIdentifier()); if (!providerMatches) { - // If the provider doesn't match, skip to the next entry. + // If the provider doesn't match, skip to the next entry continue; } - // Use the query identifier from the entry to retrieve the corresponding state parameter. + // Use the query identifier from the entry to retrieve the corresponding state parameter String stateQueryParameter = resumableState.getQueryIdentifier(); String stateFromParameter = request.getParameter(stateQueryParameter); - // Check if the `state` parameter is set. + // Check if a state parameter is set if (stateFromParameter == null || stateFromParameter.isEmpty()) { - // Remove and continue if `state` is not provided or is empty. + // Remove and continue if`state is not provided or is empty iterator.remove(); continue; } - // If the key in the entry (state) matches the state parameter provided in the request. + // If the key in the entry (state) matches the state parameter provided in the request if (entry.getKey().equals(stateFromParameter)) { - // Remove the current entry from the map. + // Remove the current entry from the map iterator.remove(); // Check if the resumableState has expired if (!resumableState.isExpired()) { - // Set the actualCredentials to the credentials from the matched entry. + // Set the actualCredentials to the credentials from the matched entry resumedCredentials = resumableState.getCredentials(); if (resumedCredentials != null) { @@ -423,7 +423,7 @@ private Credentials resumeAuthentication(Credentials credentials) { } - // Exit the loop since we've found the matching state and it's unique. + // Exit the loop since we've found the matching state and it's unique break; } } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/ResumableAuthenticationState.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/ResumableAuthenticationState.java index f295a82088..1aaa7ea579 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/ResumableAuthenticationState.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/ResumableAuthenticationState.java @@ -89,7 +89,7 @@ public ResumableAuthenticationState(String providerIdentifier, String queryIdent * indicating that the state is expired; false otherwise. */ public boolean isExpired() { - return System.currentTimeMillis() > expirationTimestamp; + return System.currentTimeMillis() >= expirationTimestamp; } /**