mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-1289: Add new translations and guacamole properties.
This commit is contained in:
@@ -102,60 +102,60 @@ public class UserVerificationService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
String redirectUrl = confService.getRedirectUrl().toString();
|
String redirectUrl = confService.getRedirectUri().toString();
|
||||||
|
|
||||||
String builtUrl = UriComponentsBuilder
|
String builtUrl = UriComponentsBuilder
|
||||||
.fromUriString(redirectUrl)
|
.fromUriString(redirectUrl)
|
||||||
.queryParam(Credentials.RESUME_QUERY, DuoAuthenticationProvider.PROVIDER_IDENTIFER)
|
.queryParam(Credentials.RESUME_QUERY, DuoAuthenticationProvider.PROVIDER_IDENTIFER)
|
||||||
.build()
|
.build()
|
||||||
.toUriString();
|
.toUriString();
|
||||||
|
|
||||||
// Set up the Duo Client
|
// Set up the Duo Client
|
||||||
Client duoClient = new Client.Builder(
|
Client duoClient = new Client.Builder(
|
||||||
confService.getClientId(),
|
confService.getClientId(),
|
||||||
confService.getClientSecret(),
|
confService.getClientSecret(),
|
||||||
confService.getAPIHostname(),
|
confService.getAPIHostname(),
|
||||||
builtUrl)
|
builtUrl)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
duoClient.healthCheck();
|
duoClient.healthCheck();
|
||||||
|
|
||||||
// Retrieve signed Duo Code and State from the request
|
// Retrieve signed Duo Code and State from the request
|
||||||
String duoCode = request.getParameter(DUO_CODE_PARAMETER_NAME);
|
String duoCode = request.getParameter(DUO_CODE_PARAMETER_NAME);
|
||||||
String duoState = request.getParameter(DUO_STATE_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 no code or state is received, assume Duo MFA redirect has not occured and do it
|
||||||
if (duoCode == null || duoState == null) {
|
if (duoCode == null || duoState == null) {
|
||||||
|
|
||||||
// Get a new session state from the Duo client
|
// Get a new session state from the Duo client
|
||||||
duoState = duoClient.generateState();
|
duoState = duoClient.generateState();
|
||||||
long expirationTimestamp = System.currentTimeMillis() + (confService.getAuthTimeout() * 1000L);
|
long expirationTimestamp = System.currentTimeMillis() + (confService.getAuthTimeout() * 1000L);
|
||||||
|
|
||||||
// Request additional credentials
|
// Request additional credentials
|
||||||
throw new TranslatableGuacamoleInsufficientCredentialsException(
|
throw new TranslatableGuacamoleInsufficientCredentialsException(
|
||||||
"Verification using Duo is required before authentication "
|
"Verification using Duo is required before authentication "
|
||||||
+ "can continue.", "LOGIN.INFO_DUO_AUTH_REQUIRED",
|
+ "can continue.", "LOGIN.INFO_DUO_AUTH_REQUIRED",
|
||||||
new CredentialsInfo(Collections.singletonList(
|
new CredentialsInfo(Collections.singletonList(
|
||||||
new RedirectField(
|
new RedirectField(
|
||||||
DUO_CODE_PARAMETER_NAME,
|
DUO_CODE_PARAMETER_NAME,
|
||||||
new URI(duoClient.createAuthUrl(username, duoState)),
|
new URI(duoClient.createAuthUrl(username, duoState)),
|
||||||
new TranslatableMessage("LOGIN.INFO_DUO_REDIRECT_PENDING")
|
new TranslatableMessage("LOGIN.INFO_DUO_REDIRECT_PENDING")
|
||||||
)
|
)
|
||||||
)),
|
)),
|
||||||
duoState, DuoAuthenticationProvider.PROVIDER_IDENTIFER,
|
duoState, DuoAuthenticationProvider.PROVIDER_IDENTIFER,
|
||||||
DUO_STATE_PARAMETER_NAME, expirationTimestamp
|
DUO_STATE_PARAMETER_NAME, expirationTimestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the token from the DuoClient using the code and username, and check status
|
// Get the token from the DuoClient using the code and username, and check status
|
||||||
Token token = duoClient.exchangeAuthorizationCodeFor2FAResult(duoCode, username);
|
Token token = duoClient.exchangeAuthorizationCodeFor2FAResult(duoCode, username);
|
||||||
if (token == null
|
if (token == null
|
||||||
|| token.getAuth_result() == null
|
|| token.getAuth_result() == null
|
||||||
|| !DUO_TOKEN_SUCCESS_VALUE.equals(token.getAuth_result().getStatus()))
|
|| !DUO_TOKEN_SUCCESS_VALUE.equals(token.getAuth_result().getStatus()))
|
||||||
throw new TranslatableGuacamoleClientException("Provided Duo "
|
throw new TranslatableGuacamoleClientException("Provided Duo "
|
||||||
+ "validation code is incorrect.",
|
+ "validation code is incorrect.",
|
||||||
"LOGIN.INFO_DUO_VALIDATION_CODE_INCORRECT");
|
"LOGIN.INFO_DUO_VALIDATION_CODE_INCORRECT");
|
||||||
}
|
}
|
||||||
catch (DuoException e) {
|
catch (DuoException e) {
|
||||||
throw new GuacamoleServerException("Duo Client error.", e);
|
throw new GuacamoleServerException("Duo Client error.", e);
|
||||||
|
@@ -55,8 +55,8 @@ public class ConfigurationService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The property within guacamole.properties which defines the integration
|
* The property within guacamole.properties which defines the client id
|
||||||
* key received from Duo for verifying Guacamole users. This value MUST be
|
* received from Duo for verifying Guacamole users. This value MUST be
|
||||||
* exactly 20 characters.
|
* exactly 20 characters.
|
||||||
*/
|
*/
|
||||||
private static final StringGuacamoleProperty DUO_CLIENT_ID =
|
private static final StringGuacamoleProperty DUO_CLIENT_ID =
|
||||||
@@ -81,15 +81,15 @@ public class ConfigurationService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* 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() {
|
new URIGuacamoleProperty() {
|
||||||
|
|
||||||
@Override
|
@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,
|
* Returns the client secret received from Duo for verifying Guacamole users,
|
||||||
* as defined in guacamole.properties by the "duo-client-secert" property.
|
* as defined in guacamole.properties by the "duo-client-secret" property.
|
||||||
* This value MUST be exactly 20 characters.
|
* This value MUST be exactly 20 characters.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
@@ -155,9 +155,9 @@ public class ConfigurationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the callback URL that will be called by Duo after authentication
|
* Return the callback URI that will be called by Duo after authentication
|
||||||
* with Duo has been completed. This should be the URL to return the user
|
* with Duo has been completed. This should be the URI to return the user
|
||||||
* to the Guacamole interface, and will be a full URL.
|
* to the Guacamole interface, and will be a full URI.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* The URL for Duo to use to callback to the Guacamole interface after
|
* 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
|
* If guacamole.properties cannot be read, or if the property is not
|
||||||
* defined.
|
* defined.
|
||||||
*/
|
*/
|
||||||
public URI getRedirectUrl() throws GuacamoleException {
|
public URI getRedirectUri() throws GuacamoleException {
|
||||||
return environment.getRequiredProperty(DUO_REDIRECT_URL);
|
return environment.getRequiredProperty(DUO_REDIRECT_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -7,7 +7,8 @@
|
|||||||
"LOGIN" : {
|
"LOGIN" : {
|
||||||
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
||||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo validation code incorrect.",
|
"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..."
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
"LOGIN" : {
|
"LOGIN" : {
|
||||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duoの認証コードが間違っています。",
|
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duoの認証コードが間違っています。",
|
||||||
"INFO_DUO_AUTH_REQUIRED" : "Duoで認証してください。"
|
"INFO_DUO_AUTH_REQUIRED" : "Duoで認証してください。",
|
||||||
|
"INFO_DUO_REDIRECT_PENDING" : "Duoへリダイレクトしています。"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -845,14 +845,13 @@ associate_totp() {
|
|||||||
|
|
||||||
##
|
##
|
||||||
## Adds properties to guacamole.properties which configure the Duo two-factor
|
## Adds properties to guacamole.properties which configure the Duo two-factor
|
||||||
## authentication service. Checks to see if all variables are defined and makes sure
|
## authentication service. Checks to see if all variables are defined
|
||||||
## DUO_APPLICATION_KEY is >= 40 characters.
|
|
||||||
##
|
##
|
||||||
associate_duo() {
|
associate_duo() {
|
||||||
# Verify required parameters are present
|
# Verify required parameters are present
|
||||||
if [ -z "$DUO_INTEGRATION_KEY" ] || \
|
if [ -z "$DUO_CLIENT_ID" ] || \
|
||||||
[ -z "$DUO_SECRET_KEY" ] || \
|
[ -z "$DUO_CLIENT_SECRET" ] || \
|
||||||
[ ${#DUO_APPLICATION_KEY} -lt 40 ]
|
[ -z "$DUO_REDIRECT_URI" ]
|
||||||
then
|
then
|
||||||
cat <<END
|
cat <<END
|
||||||
FATAL: Missing required environment variables
|
FATAL: Missing required environment variables
|
||||||
@@ -862,21 +861,20 @@ following environment variables:
|
|||||||
|
|
||||||
DUO_API_HOSTNAME The hostname of the Duo API endpoint.
|
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.
|
DUO_REDIRECT_URI The URI to redirect back to upon successful authentication.
|
||||||
This value must be at least 40 characters.
|
|
||||||
END
|
END
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update config file
|
# Update config file
|
||||||
set_property "duo-api-hostname" "$DUO_API_HOSTNAME"
|
set_property "duo-api-hostname" "$DUO_API_HOSTNAME"
|
||||||
set_property "duo-integration-key" "$DUO_INTEGRATION_KEY"
|
set_property "duo-client-id" "$DUO_CLIENT_ID"
|
||||||
set_property "duo-secret-key" "$DUO_SECRET_KEY"
|
set_property "duo-client-secret" "$DUO_CLIENT_SECRET"
|
||||||
set_property "duo-application-key" "$DUO_APPLICATION_KEY"
|
set_property "duo-redirect-uri" "$DUO_REDIRECT_URI"
|
||||||
|
|
||||||
# Add required .jar files to GUACAMOLE_EXT
|
# Add required .jar files to GUACAMOLE_EXT
|
||||||
ln -s /opt/guacamole/duo/guacamole-auth-*.jar "$GUACAMOLE_EXT"
|
ln -s /opt/guacamole/duo/guacamole-auth-*.jar "$GUACAMOLE_EXT"
|
||||||
|
@@ -28,36 +28,36 @@ package org.apache.guacamole.net.auth.credentials;
|
|||||||
*/
|
*/
|
||||||
public class GuacamoleInsufficientCredentialsException extends GuacamoleCredentialsException {
|
public class GuacamoleInsufficientCredentialsException extends GuacamoleCredentialsException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default state token to use when no specific state information is provided.
|
* The default state token to use when no specific state information is provided.
|
||||||
*/
|
*/
|
||||||
private static final String DEFAULT_STATE = "";
|
private static final String DEFAULT_STATE = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default provider identifier to use when no specific provider is identified.
|
* The default provider identifier to use when no specific provider is identified.
|
||||||
* This serves as a placeholder indicating that either no specific provider is
|
* This serves as a placeholder indicating that either no specific provider is
|
||||||
* responsible for the exception or the responsible provider has not been identified.
|
* responsible for the exception or the responsible provider has not been identified.
|
||||||
*/
|
*/
|
||||||
private static final String DEFAULT_PROVIDER_IDENTIFIER = "";
|
private static final String DEFAULT_PROVIDER_IDENTIFIER = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default query identifier to use when no specific query is identified.
|
* 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
|
* This serves as a placeholder and indicates that the specific query related to
|
||||||
* the provider's state resume operation has not been provided.
|
* the provider's state resume operation has not been provided.
|
||||||
*/
|
*/
|
||||||
private static final String DEFAULT_QUERY_IDENTIFIER = "";
|
private static final String DEFAULT_QUERY_IDENTIFIER = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default expiration timestamp to use when no specific expiration is provided,
|
* The default expiration timestamp to use when no specific expiration is provided,
|
||||||
* effectively indicating that the state token does not expire.
|
* effectively indicating that the state token does not expire.
|
||||||
*/
|
*/
|
||||||
private static final long DEFAULT_EXPIRES = -1L;
|
private static final long DEFAULT_EXPIRES = -1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An opaque value that may be used by a client to maintain state across requests
|
* An opaque value that may be used by a client to maintain state across requests
|
||||||
* which are part of the same authentication transaction.
|
* which are part of the same authentication transaction.
|
||||||
*/
|
*/
|
||||||
protected final String state;
|
protected final String state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The identifier for the authentication provider that threw this exception.
|
* The identifier for the authentication provider that threw this exception.
|
||||||
@@ -73,12 +73,12 @@ protected final String state;
|
|||||||
*/
|
*/
|
||||||
protected final String queryIdentifier;
|
protected final String queryIdentifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timestamp after which the state token associated with the authentication process
|
* 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
|
* should no longer be considered valid, expressed as the number of milliseconds since
|
||||||
* UNIX epoch.
|
* UNIX epoch.
|
||||||
*/
|
*/
|
||||||
protected final long expires;
|
protected final long expires;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new GuacamoleInsufficientCredentialsException with the specified
|
* Creates a new GuacamoleInsufficientCredentialsException with the specified
|
||||||
@@ -107,9 +107,9 @@ protected final long expires;
|
|||||||
* authentication process should no longer be considered valid, expressed
|
* authentication process should no longer be considered valid, expressed
|
||||||
* as the number of milliseconds since UNIX epoch.
|
* as the number of milliseconds since UNIX epoch.
|
||||||
*/
|
*/
|
||||||
public GuacamoleInsufficientCredentialsException(String message,
|
public GuacamoleInsufficientCredentialsException(String message,
|
||||||
CredentialsInfo credentialsInfo, String state, String providerIdentifier, String queryIdentifier,
|
CredentialsInfo credentialsInfo, String state,
|
||||||
long expires) {
|
String providerIdentifier, String queryIdentifier, long expires) {
|
||||||
super(message, credentialsInfo);
|
super(message, credentialsInfo);
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.providerIdentifier = providerIdentifier;
|
this.providerIdentifier = providerIdentifier;
|
||||||
|
@@ -364,7 +364,7 @@ public class AuthenticationService {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* Resumed credentials if a valid resumable state is found; otherwise,
|
* Resumed credentials if a valid resumable state is found; otherwise,
|
||||||
* returns {@code null}.
|
* returns null.
|
||||||
*/
|
*/
|
||||||
private Credentials resumeAuthentication(Credentials credentials) {
|
private Credentials resumeAuthentication(Credentials credentials) {
|
||||||
|
|
||||||
@@ -373,11 +373,11 @@ public class AuthenticationService {
|
|||||||
// Retrieve signed State from the request
|
// Retrieve signed State from the request
|
||||||
HttpServletRequest request = credentials.getRequest();
|
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);
|
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()) {
|
if (resumableProviderId == null || resumableProviderId.isEmpty()) {
|
||||||
// return if a provider id is not set.
|
// Return if a provider id is not set
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,34 +387,34 @@ public class AuthenticationService {
|
|||||||
Map.Entry<String, ResumableAuthenticationState> entry = iterator.next();
|
Map.Entry<String, ResumableAuthenticationState> entry = iterator.next();
|
||||||
ResumableAuthenticationState resumableState = entry.getValue();
|
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());
|
boolean providerMatches = resumableProviderId.equals(resumableState.getProviderIdentifier());
|
||||||
if (!providerMatches) {
|
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;
|
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 stateQueryParameter = resumableState.getQueryIdentifier();
|
||||||
String stateFromParameter = request.getParameter(stateQueryParameter);
|
String stateFromParameter = request.getParameter(stateQueryParameter);
|
||||||
|
|
||||||
// Check if the `state` parameter is set.
|
// Check if a state parameter is set
|
||||||
if (stateFromParameter == null || stateFromParameter.isEmpty()) {
|
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();
|
iterator.remove();
|
||||||
continue;
|
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)) {
|
if (entry.getKey().equals(stateFromParameter)) {
|
||||||
|
|
||||||
// Remove the current entry from the map.
|
// Remove the current entry from the map
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
|
|
||||||
// Check if the resumableState has expired
|
// Check if the resumableState has expired
|
||||||
if (!resumableState.isExpired()) {
|
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();
|
resumedCredentials = resumableState.getCredentials();
|
||||||
|
|
||||||
if (resumedCredentials != null) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -89,7 +89,7 @@ public class ResumableAuthenticationState {
|
|||||||
* indicating that the state is expired; false otherwise.
|
* indicating that the state is expired; false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isExpired() {
|
public boolean isExpired() {
|
||||||
return System.currentTimeMillis() > expirationTimestamp;
|
return System.currentTimeMillis() >= expirationTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user