GUACAMOLE-1289: Add new translations and guacamole properties.

This commit is contained in:
Alex Leitner
2024-04-04 01:32:48 +00:00
parent 7c49466c79
commit 9f1a8e6686
8 changed files with 127 additions and 127 deletions

View File

@@ -102,60 +102,60 @@ public class UserVerificationService {
try {
String redirectUrl = confService.getRedirectUrl().toString();
String redirectUrl = confService.getRedirectUri().toString();
String builtUrl = UriComponentsBuilder
.fromUriString(redirectUrl)
.queryParam(Credentials.RESUME_QUERY, DuoAuthenticationProvider.PROVIDER_IDENTIFER)
.build()
.toUriString();
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);
// Set up the Duo Client
Client duoClient = new Client.Builder(
confService.getClientId(),
confService.getClientSecret(),
confService.getAPIHostname(),
builtUrl)
.build();
// If no code or state is received, assume Duo MFA redirect has not occured and do it.
if (duoCode == null || duoState == null) {
duoClient.healthCheck();
// Get a new session state from the Duo client
duoState = duoClient.generateState();
long expirationTimestamp = System.currentTimeMillis() + (confService.getAuthTimeout() * 1000L);
// 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);
// 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
);
// If no code or state is received, assume Duo MFA redirect has not occured and do it
if (duoCode == null || duoState == null) {
}
// 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");
// 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);

View File

@@ -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 class ConfigurationService {
}
/**
* 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 class ConfigurationService {
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 class ConfigurationService {
* 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);
}
/**

View File

@@ -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..."
}
}

View File

@@ -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へリダイレクトしています。"
}
}

View File

@@ -845,14 +845,13 @@ associate_totp() {
##
## Adds properties to guacamole.properties which configure the Duo two-factor
## authentication service. Checks to see if all variables are defined and makes sure
## DUO_APPLICATION_KEY is >= 40 characters.
## authentication service. Checks to see if all variables are defined
##
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" ] || \
[ -z "$DUO_REDIRECT_URI" ]
then
cat <<END
FATAL: Missing required environment variables
@@ -862,21 +861,20 @@ following environment variables:
DUO_API_HOSTNAME The hostname of the Duo API endpoint.
DUO_INTEGRATION_KEY The integration key provided for Guacamole by Duo.
DUO_CLIENT_ID The client id (or integration key) provided for Guacamole by Duo.
DUO_SECRET_KEY The secret key provided for Guacamole by Duo.
DUO_CLIENT_SECRET The secret key provided for Guacamole by Duo.
DUO_APPLICATION_KEY An arbitrary, random key.
This value must be at least 40 characters.
DUO_REDIRECT_URI The URI to redirect back to upon successful authentication.
END
exit 1;
fi
# Update config file
set_property "duo-api-hostname" "$DUO_API_HOSTNAME"
set_property "duo-integration-key" "$DUO_INTEGRATION_KEY"
set_property "duo-secret-key" "$DUO_SECRET_KEY"
set_property "duo-application-key" "$DUO_APPLICATION_KEY"
set_property "duo-client-id" "$DUO_CLIENT_ID"
set_property "duo-client-secret" "$DUO_CLIENT_SECRET"
set_property "duo-redirect-uri" "$DUO_REDIRECT_URI"
# Add required .jar files to GUACAMOLE_EXT
ln -s /opt/guacamole/duo/guacamole-auth-*.jar "$GUACAMOLE_EXT"

View File

@@ -28,36 +28,36 @@ package org.apache.guacamole.net.auth.credentials;
*/
public class GuacamoleInsufficientCredentialsException extends GuacamoleCredentialsException {
/**
* The default state token to use when no specific state information is provided.
*/
private static final String DEFAULT_STATE = "";
/**
* The default state token to use when no specific state information is provided.
*/
private static final String DEFAULT_STATE = "";
/**
* The default provider identifier to use when no specific provider is identified.
* This serves as a placeholder indicating that either no specific provider is
* responsible for the exception or the responsible provider has not been identified.
*/
private static final String DEFAULT_PROVIDER_IDENTIFIER = "";
/**
* The default provider identifier to use when no specific provider is identified.
* This serves as a placeholder indicating that either no specific provider is
* responsible for the exception or the responsible provider has not been identified.
*/
private static final String DEFAULT_PROVIDER_IDENTIFIER = "";
/**
* The default query identifier to use when no specific query is identified.
* This serves as a placeholder and indicates that the specific query related to
* the provider's state resume operation has not been provided.
*/
private static final String DEFAULT_QUERY_IDENTIFIER = "";
/**
* The default query identifier to use when no specific query is identified.
* This serves as a placeholder and indicates that the specific query related to
* the provider's state resume operation has not been provided.
*/
private static final String DEFAULT_QUERY_IDENTIFIER = "";
/**
* The default expiration timestamp to use when no specific expiration is provided,
* effectively indicating that the state token does not expire.
*/
private static final long DEFAULT_EXPIRES = -1L;
/**
* The default expiration timestamp to use when no specific expiration is provided,
* effectively indicating that the state token does not expire.
*/
private static final long DEFAULT_EXPIRES = -1L;
/**
* An opaque value that may be used by a client to maintain state across requests
* which are part of the same authentication transaction.
*/
protected final String state;
/**
* An opaque value that may be used by a client to maintain state across requests
* which are part of the same authentication transaction.
*/
protected final String state;
/**
* The identifier for the authentication provider that threw this exception.
@@ -73,12 +73,12 @@ protected final String state;
*/
protected final String queryIdentifier;
/**
* The timestamp after which the state token associated with the authentication process
* should no longer be considered valid, expressed as the number of milliseconds since
* UNIX epoch.
*/
protected final long expires;
/**
* The timestamp after which the state token associated with the authentication process
* should no longer be considered valid, expressed as the number of milliseconds since
* UNIX epoch.
*/
protected final long expires;
/**
* Creates a new GuacamoleInsufficientCredentialsException with the specified
@@ -107,9 +107,9 @@ protected final long expires;
* authentication process should no longer be considered valid, expressed
* as the number of milliseconds since UNIX epoch.
*/
public GuacamoleInsufficientCredentialsException(String message,
CredentialsInfo credentialsInfo, String state, String providerIdentifier, String queryIdentifier,
long expires) {
public GuacamoleInsufficientCredentialsException(String message,
CredentialsInfo credentialsInfo, String state,
String providerIdentifier, String queryIdentifier, long expires) {
super(message, credentialsInfo);
this.state = state;
this.providerIdentifier = providerIdentifier;

View File

@@ -364,7 +364,7 @@ public class AuthenticationService {
*
* @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 @@ public class AuthenticationService {
// 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 @@ public class AuthenticationService {
Map.Entry<String, ResumableAuthenticationState> 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 @@ public class AuthenticationService {
}
// 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;
}
}

View File

@@ -89,7 +89,7 @@ public class ResumableAuthenticationState {
* indicating that the state is expired; false otherwise.
*/
public boolean isExpired() {
return System.currentTimeMillis() > expirationTimestamp;
return System.currentTimeMillis() >= expirationTimestamp;
}
/**