+ guacamole-auth-ban
guacamole-auth-duo
guacamole-auth-header
guacamole-auth-jdbc
diff --git a/guacamole-docker/bin/build-guacamole.sh b/guacamole-docker/bin/build-guacamole.sh
index 9f1773d83..6ffc86611 100755
--- a/guacamole-docker/bin/build-guacamole.sh
+++ b/guacamole-docker/bin/build-guacamole.sh
@@ -198,3 +198,13 @@ if [ -f extensions/guacamole-auth-json/target/guacamole-auth-json*.jar ]; then
mkdir -p "$DESTINATION/json"
cp extensions/guacamole-auth-json/target/guacamole-auth-json*.jar "$DESTINATION/json"
fi
+
+#
+# Copy automatic brute-force banning auth extension if it was built
+#
+
+if [ -f extensions/guacamole-auth-ban/target/guacamole-auth-ban*.jar ]; then
+ mkdir -p "$DESTINATION/ban"
+ cp extensions/guacamole-auth-ban/target/guacamole-auth-ban*.jar "$DESTINATION/ban"
+fi
+
diff --git a/guacamole-docker/bin/start.sh b/guacamole-docker/bin/start.sh
index c9d205b58..632c3d03a 100755
--- a/guacamole-docker/bin/start.sh
+++ b/guacamole-docker/bin/start.sh
@@ -1160,6 +1160,18 @@ if [ -n "$API_SESSION_TIMEOUT" ]; then
associate_apisessiontimeout
fi
+# Apply any overrides for default address ban behavior
+set_optional_property "ban-address-duration" "$BAN_ADDRESS_DURATION"
+set_optional_property "ban-max-addresses" "$BAN_MAX_ADDRESSES"
+set_optional_property "ban-max-invalid-attempts" "$BAN_MAX_INVALID_ATTEMPTS"
+
+# Ensure guacamole-auth-ban always loads before other extensions unless
+# explicitly overridden via naming or EXTENSION_PRIORITY (allowing other
+# extensions to attempt authentication before guacamole-auth-ban has a chance
+# to enforce any bans could allow credentials to continue to be guessed even
+# after the address has been blocked via timing attacks)
+ln -s /opt/guacamole/ban/guacamole-auth-*.jar "$GUACAMOLE_EXT/_guacamole-auth-ban.jar"
+
# Set logback level if specified
if [ -n "$LOGBACK_LEVEL" ]; then
unzip -o -j /opt/guacamole/guacamole.war WEB-INF/classes/logback.xml -d $GUACAMOLE_HOME
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationFailureEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationFailureEvent.java
index 9808e7047..870590076 100644
--- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationFailureEvent.java
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationFailureEvent.java
@@ -19,28 +19,91 @@
package org.apache.guacamole.net.event;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.event.listener.Listener;
/**
* An event which is triggered whenever a user's credentials fail to be
* authenticated. The credentials that failed to be authenticated are included
* within this event, and can be retrieved using getCredentials().
*/
-public class AuthenticationFailureEvent implements CredentialEvent {
+public class AuthenticationFailureEvent implements AuthenticationProviderEvent,
+ CredentialEvent, FailureEvent {
/**
* The credentials which failed authentication.
*/
- private Credentials credentials;
+ private final Credentials credentials;
/**
- * Creates a new AuthenticationFailureEvent which represents the failure
- * to authenticate the given credentials.
+ * The AuthenticationProvider that encountered the failure. This may be
+ * null if the AuthenticationProvider is not known, such as if the failure
+ * is caused by every AuthenticationProvider passively refusing to
+ * authenticate the user but without explicitly rejecting the user
+ * (returning null for calls to {@link AuthenticationProvider#authenticateUser(org.apache.guacamole.net.auth.Credentials)}),
+ * or if the failure is external to any installed AuthenticationProvider
+ * (such as within a {@link Listener}.
+ */
+ private final AuthenticationProvider authProvider;
+
+ /**
+ * The Throwable that was thrown resulting in the failure, if any. This
+ * may be null if authentication failed without a known error, such as if
+ * the failure is caused by every AuthenticationProvider passively refusing
+ * to authenticate the user but without explicitly rejecting the user
+ * (returning null for calls to {@link AuthenticationProvider#authenticateUser(org.apache.guacamole.net.auth.Credentials)}).
+ */
+ private final Throwable failure;
+
+ /**
+ * Creates a new AuthenticationFailureEvent which represents a failure
+ * to authenticate the given credentials where there is no specific
+ * AuthenticationProvider nor Throwable associated with the failure.
*
- * @param credentials The credentials which failed authentication.
+ * @param credentials
+ * The credentials which failed authentication.
*/
public AuthenticationFailureEvent(Credentials credentials) {
+ this(credentials, null);
+ }
+
+ /**
+ * Creates a new AuthenticationFailureEvent which represents a failure
+ * to authenticate the given credentials where there is no specific
+ * AuthenticationProvider causing the failure.
+ *
+ * @param credentials
+ * The credentials which failed authentication.
+ *
+ * @param failure
+ * The Throwable that was thrown resulting in the failure, or null if
+ * there is no such Throwable.
+ */
+ public AuthenticationFailureEvent(Credentials credentials, Throwable failure) {
+ this(credentials, null, failure);
+ }
+
+ /**
+ * Creates a new AuthenticationFailureEvent which represents a failure
+ * to authenticate the given credentials.
+ *
+ * @param credentials
+ * The credentials which failed authentication.
+ *
+ * @param authProvider
+ * The AuthenticationProvider that caused the failure, or null if there
+ * is no such AuthenticationProvider.
+ *
+ * @param failure
+ * The Throwable that was thrown resulting in the failure, or null if
+ * there is no such Throwable.
+ */
+ public AuthenticationFailureEvent(Credentials credentials,
+ AuthenticationProvider authProvider, Throwable failure) {
this.credentials = credentials;
+ this.authProvider = authProvider;
+ this.failure = failure;
}
@Override
@@ -48,4 +111,35 @@ public class AuthenticationFailureEvent implements CredentialEvent {
return credentials;
}
+ /**
+ * {@inheritDoc}
+ *
+ * NOTE: In the case of an authentication failure, cases where this may
+ * be null include if authentication failed without a definite single
+ * AuthenticationProvider causing that failure, such as if the failure is
+ * caused by every AuthenticationProvider passively refusing to
+ * authenticate the user but without explicitly rejecting the user
+ * (returning null for calls to {@link AuthenticationProvider#authenticateUser(org.apache.guacamole.net.auth.Credentials)}),
+ * or if the failure is external to any installed AuthenticationProvider
+ * (such as within a {@link Listener}.
+ */
+ @Override
+ public AuthenticationProvider getAuthenticationProvider() {
+ return authProvider;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
NOTE: In the case of an authentication failure, cases where this may
+ * be null include if authentication failed without a known error, such as
+ * if the failure is caused by every AuthenticationProvider passively
+ * refusing to authenticate the user but without explicitly rejecting the
+ * user (returning null for calls to {@link AuthenticationProvider#authenticateUser(org.apache.guacamole.net.auth.Credentials)}).
+ */
+ @Override
+ public Throwable getFailure() {
+ return failure;
+ }
+
}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationProviderEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationProviderEvent.java
new file mode 100644
index 000000000..7faa87697
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationProviderEvent.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.net.event;
+
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+
+/**
+ * An event which may be dispatched due to a specific AuthenticationProvider.
+ */
+public interface AuthenticationProviderEvent {
+
+ /**
+ * Returns the AuthenticationProvider that resulted in the event, if any.
+ * If the event occurred without any definite causing
+ * AuthenticationProvider, this may be null.
+ *
+ * @return
+ * The AuthenticationProvider that resulted in the event, or null if no
+ * such AuthenticationProvider is known.
+ */
+ AuthenticationProvider getAuthenticationProvider();
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java
index 8b63bcf0e..a9b21dce8 100644
--- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java
@@ -20,6 +20,7 @@
package org.apache.guacamole.net.event;
import org.apache.guacamole.net.auth.AuthenticatedUser;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
/**
@@ -32,7 +33,8 @@ import org.apache.guacamole.net.auth.Credentials;
* is effectively vetoed and will be subsequently processed as though the
* authentication failed.
*/
-public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent {
+public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent,
+ AuthenticationProviderEvent {
/**
* The AuthenticatedUser identifying the user that successfully
@@ -60,7 +62,12 @@ public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent {
@Override
public Credentials getCredentials() {
- return authenticatedUser.getCredentials();
+ return getAuthenticatedUser().getCredentials();
+ }
+
+ @Override
+ public AuthenticationProvider getAuthenticationProvider() {
+ return getAuthenticatedUser().getAuthenticationProvider();
}
}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/FailureEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/FailureEvent.java
new file mode 100644
index 000000000..dfc337594
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/FailureEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.net.event;
+
+/**
+ * An event which represents failure of an operation, where that failure may
+ * be associated with a particular Throwable.
+ */
+public interface FailureEvent {
+
+ /**
+ * Returns the Throwable that represents the failure that occurred, if any.
+ * If the failure was recognized but without a definite known error, this
+ * may be null.
+ *
+ * @return
+ * The Throwable that represents the failure that occurred, or null if
+ * no such Throwable is known.
+ */
+ Throwable getFailure();
+
+}
diff --git a/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js b/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js
index 944fd4a9a..199d6cb2e 100644
--- a/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js
+++ b/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js
@@ -198,12 +198,16 @@ angular.module('auth').factory('authenticationService', ['$injector',
['catch'](requestService.createErrorCallback(function authenticationFailed(error) {
// Request credentials if provided credentials were invalid
- if (error.type === Error.Type.INVALID_CREDENTIALS)
+ if (error.type === Error.Type.INVALID_CREDENTIALS) {
$rootScope.$broadcast('guacInvalidCredentials', parameters, error);
+ clearAuthenticationResult();
+ }
// Request more credentials if provided credentials were not enough
- else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS)
+ else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) {
$rootScope.$broadcast('guacInsufficientCredentials', parameters, error);
+ clearAuthenticationResult();
+ }
// Abort rendering of page if an internal error occurs
else if (error.type === Error.Type.INTERNAL_ERROR)
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java
index ce8a9fb0c..8ed76c6a6 100644
--- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java
@@ -34,7 +34,6 @@ import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
-import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
@@ -169,14 +168,15 @@ public class AuthenticationService {
* The AuthenticatedUser given by the highest-priority
* AuthenticationProvider for which the given credentials are valid.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If the given credentials are not valid for any
* AuthenticationProvider, or if an error occurs while authenticating
* the user.
*/
private AuthenticatedUser authenticateUser(Credentials credentials)
- throws GuacamoleException {
+ throws GuacamoleAuthenticationProcessException {
+ AuthenticationProvider failedAuthProvider = null;
GuacamoleCredentialsException authFailure = null;
// Attempt authentication against each AuthenticationProvider
@@ -191,27 +191,29 @@ public class AuthenticationService {
// Insufficient credentials should take precedence
catch (GuacamoleInsufficientCredentialsException e) {
- if (authFailure == null || authFailure instanceof GuacamoleInvalidCredentialsException)
+ if (authFailure == null || authFailure instanceof GuacamoleInvalidCredentialsException) {
+ failedAuthProvider = authProvider;
authFailure = e;
+ }
}
-
+
// Catch other credentials exceptions and assign the first one
catch (GuacamoleCredentialsException e) {
- if (authFailure == null)
+ if (authFailure == null) {
+ failedAuthProvider = authProvider;
authFailure = e;
+ }
+ }
+
+ catch (GuacamoleException | RuntimeException | Error e) {
+ throw new GuacamoleAuthenticationProcessException("User "
+ + "authentication was aborted.", authProvider, e);
}
}
- // If a specific failure occured, rethrow that
- if (authFailure != null)
- throw authFailure;
-
- // Otherwise, request standard username/password
- throw new GuacamoleInvalidCredentialsException(
- "Permission Denied.",
- CredentialsInfo.USERNAME_PASSWORD
- );
+ throw new GuacamoleAuthenticationProcessException("User authentication "
+ + "failed.", failedAuthProvider, authFailure);
}
@@ -230,51 +232,29 @@ public class AuthenticationService {
* A AuthenticatedUser which may have been updated due to re-
* authentication.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If an error prevents the user from being re-authenticated.
*/
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
// Get original AuthenticationProvider
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
- // Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
- authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
- if (authenticatedUser == null)
- throw new GuacamoleSecurityException("User re-authentication failed.");
+ try {
- return authenticatedUser;
+ // Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
+ authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
+ if (authenticatedUser == null)
+ throw new GuacamoleSecurityException("User re-authentication failed.");
- }
+ return authenticatedUser;
- /**
- * Notify all bound listeners that a successful authentication
- * has occurred.
- *
- * @param authenticatedUser
- * The user that was successfully authenticated.
- *
- * @throws GuacamoleException
- * If thrown by a listener.
- */
- private void fireAuthenticationSuccessEvent(AuthenticatedUser authenticatedUser)
- throws GuacamoleException {
- listenerService.handleEvent(new AuthenticationSuccessEvent(authenticatedUser));
- }
+ }
+ catch (GuacamoleException | RuntimeException | Error e) {
+ throw new GuacamoleAuthenticationProcessException("User re-authentication failed.", authProvider, e);
+ }
- /**
- * Notify all bound listeners that an authentication attempt has failed.
- *
- * @param credentials
- * The credentials that failed to authenticate.
- *
- * @throws GuacamoleException
- * If thrown by a listener.
- */
- private void fireAuthenticationFailedEvent(Credentials credentials)
- throws GuacamoleException {
- listenerService.handleEvent(new AuthenticationFailureEvent(credentials));
}
/**
@@ -292,61 +272,23 @@ public class AuthenticationService {
* The AuthenticatedUser associated with the given session and
* credentials.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If an error occurs while authenticating or re-authenticating the
* user.
*/
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
- Credentials credentials) throws GuacamoleException {
-
- try {
-
- // Re-authenticate user if session exists
- if (existingSession != null) {
- AuthenticatedUser updatedUser = updateAuthenticatedUser(
- existingSession.getAuthenticatedUser(), credentials);
- fireAuthenticationSuccessEvent(updatedUser);
- return updatedUser;
- }
-
- // Otherwise, attempt authentication as a new user
- AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
- fireAuthenticationSuccessEvent(authenticatedUser);
-
- if (logger.isInfoEnabled())
- logger.info("User \"{}\" successfully authenticated from {}.",
- authenticatedUser.getIdentifier(),
- getLoggableAddress(credentials.getRequest()));
-
- return authenticatedUser;
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
+ // Re-authenticate user if session exists
+ if (existingSession != null) {
+ AuthenticatedUser updatedUser = updateAuthenticatedUser(
+ existingSession.getAuthenticatedUser(), credentials);
+ return updatedUser;
}
- // Log and rethrow any authentication errors
- catch (GuacamoleException e) {
-
- fireAuthenticationFailedEvent(credentials);
-
- // Get request and username for sake of logging
- HttpServletRequest request = credentials.getRequest();
- String username = credentials.getUsername();
-
- // Log authentication failures with associated usernames
- if (username != null) {
- if (logger.isWarnEnabled())
- logger.warn("Authentication attempt from {} for user \"{}\" failed.",
- getLoggableAddress(request), username);
- }
-
- // Log anonymous authentication failures
- else if (logger.isDebugEnabled())
- logger.debug("Anonymous authentication attempt from {} failed.",
- getLoggableAddress(request));
-
- // Rethrow exception
- throw e;
-
- }
+ // Otherwise, attempt authentication as a new user
+ AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
+ return authenticatedUser;
}
@@ -371,15 +313,14 @@ public class AuthenticationService {
* A List of all UserContexts associated with the given
* AuthenticatedUser.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If an error occurs while creating or updating any UserContext.
*/
private List getUserContexts(GuacamoleSession existingSession,
AuthenticatedUser authenticatedUser, Credentials credentials)
- throws GuacamoleException {
+ throws GuacamoleAuthenticationProcessException {
- List userContexts =
- new ArrayList(authProviders.size());
+ List userContexts = new ArrayList<>(authProviders.size());
// If UserContexts already exist, update them and add to the list
if (existingSession != null) {
@@ -392,7 +333,15 @@ public class AuthenticationService {
// Update existing UserContext
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
- UserContext updatedUserContext = authProvider.updateUserContext(oldUserContext, authenticatedUser, credentials);
+ UserContext updatedUserContext;
+ try {
+ updatedUserContext = authProvider.updateUserContext(oldUserContext, authenticatedUser, credentials);
+ }
+ catch (GuacamoleException | RuntimeException | Error e) {
+ throw new GuacamoleAuthenticationProcessException("User "
+ + "authentication aborted during UserContext update.",
+ authProvider, e);
+ }
// Add to available data, if successful
if (updatedUserContext != null)
@@ -415,7 +364,15 @@ public class AuthenticationService {
for (AuthenticationProvider authProvider : authProviders) {
// Generate new UserContext
- UserContext userContext = authProvider.getUserContext(authenticatedUser);
+ UserContext userContext;
+ try {
+ userContext = authProvider.getUserContext(authenticatedUser);
+ }
+ catch (GuacamoleException | RuntimeException | Error e) {
+ throw new GuacamoleAuthenticationProcessException("User "
+ + "authentication aborted during initial "
+ + "UserContext creation.", authProvider, e);
+ }
// Add to available data, if successful
if (userContext != null)
@@ -453,7 +410,7 @@ public class AuthenticationService {
* If the authentication or re-authentication attempt fails.
*/
public String authenticate(Credentials credentials, String token)
- throws GuacamoleException {
+ throws GuacamoleException {
// Pull existing session if token provided
GuacamoleSession existingSession;
@@ -462,25 +419,72 @@ public class AuthenticationService {
else
existingSession = null;
- // Get up-to-date AuthenticatedUser and associated UserContexts
- AuthenticatedUser authenticatedUser = getAuthenticatedUser(existingSession, credentials);
- List userContexts = getUserContexts(existingSession, authenticatedUser, credentials);
-
- // Update existing session, if it exists
+ AuthenticatedUser authenticatedUser;
String authToken;
- if (existingSession != null) {
- authToken = token;
- existingSession.setAuthenticatedUser(authenticatedUser);
- existingSession.setUserContexts(userContexts);
+
+ try {
+
+ // Get up-to-date AuthenticatedUser and associated UserContexts
+ authenticatedUser = getAuthenticatedUser(existingSession, credentials);
+ List userContexts = getUserContexts(existingSession, authenticatedUser, credentials);
+
+ // Update existing session, if it exists
+ if (existingSession != null) {
+ authToken = token;
+ existingSession.setAuthenticatedUser(authenticatedUser);
+ existingSession.setUserContexts(userContexts);
+ }
+
+ // If no existing session, generate a new token/session pair
+ else {
+ authToken = authTokenGenerator.getToken();
+ tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
+ logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
+ }
+
+ // Report authentication success
+ try {
+ listenerService.handleEvent(new AuthenticationSuccessEvent(authenticatedUser));
+ }
+ catch (GuacamoleException e) {
+ throw new GuacamoleAuthenticationProcessException("User "
+ + "authentication aborted by event listener.", null, e);
+ }
+
}
- // If no existing session, generate a new token/session pair
- else {
- authToken = authTokenGenerator.getToken();
- tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
- logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
+ // Log and rethrow any authentication errors
+ catch (GuacamoleAuthenticationProcessException e) {
+
+ // Get request and username for sake of logging
+ HttpServletRequest request = credentials.getRequest();
+ String username = credentials.getUsername();
+
+ listenerService.handleEvent(new AuthenticationFailureEvent(credentials,
+ e.getAuthenticationProvider(), e.getCause()));
+
+ // Log authentication failures with associated usernames
+ if (username != null) {
+ if (logger.isWarnEnabled())
+ logger.warn("Authentication attempt from {} for user \"{}\" failed.",
+ getLoggableAddress(request), username);
+ }
+
+ // Log anonymous authentication failures
+ else if (logger.isDebugEnabled())
+ logger.debug("Anonymous authentication attempt from {} failed.",
+ getLoggableAddress(request));
+
+ // Rethrow exception
+ throw e.getCauseAsGuacamoleException();
+
}
+ if (logger.isInfoEnabled())
+ logger.info("User \"{}\" successfully authenticated from {}.",
+ authenticatedUser.getIdentifier(),
+ getLoggableAddress(credentials.getRequest()));
+
return authToken;
}
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/DecoratedUserContext.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/DecoratedUserContext.java
index d773068f3..2be283ac2 100644
--- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/DecoratedUserContext.java
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/DecoratedUserContext.java
@@ -76,21 +76,29 @@ public class DecoratedUserContext extends DelegatingUserContext {
* given AuthenticationProvider, or the original UserContext if the
* given AuthenticationProvider originated the UserContext.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If the given AuthenticationProvider fails while decorating the
* UserContext.
*/
private static UserContext decorate(AuthenticationProvider authProvider,
UserContext userContext, AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
// Skip the AuthenticationProvider which produced the UserContext
// being decorated
if (authProvider != userContext.getAuthenticationProvider()) {
// Apply layer of wrapping around UserContext
- UserContext decorated = authProvider.decorate(userContext,
- authenticatedUser, credentials);
+ UserContext decorated;
+ try {
+ decorated = authProvider.decorate(userContext,
+ authenticatedUser, credentials);
+ }
+ catch (GuacamoleException | RuntimeException | Error e) {
+ throw new GuacamoleAuthenticationProcessException("User "
+ + "authentication aborted by decorating UserContext.",
+ authProvider, e);
+ }
// Do not allow misbehaving extensions to wipe out the
// UserContext entirely
@@ -130,13 +138,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
* given AuthenticationProvider, or the original UserContext if the
* given AuthenticationProvider originated the UserContext.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If the given AuthenticationProvider fails while decorating the
* UserContext.
*/
private static UserContext redecorate(DecoratedUserContext decorated,
UserContext userContext, AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
AuthenticationProvider authProvider = decorated.getDecoratingAuthenticationProvider();
@@ -145,8 +153,16 @@ public class DecoratedUserContext extends DelegatingUserContext {
if (authProvider != userContext.getAuthenticationProvider()) {
// Apply next layer of wrapping around UserContext
- UserContext redecorated = authProvider.redecorate(decorated,
- userContext, authenticatedUser, credentials);
+ UserContext redecorated;
+ try {
+ redecorated = authProvider.redecorate(decorated.getDelegateUserContext(),
+ userContext, authenticatedUser, credentials);
+ }
+ catch (GuacamoleException | RuntimeException | Error e) {
+ throw new GuacamoleAuthenticationProcessException("User "
+ + "authentication aborted by redecorating UserContext.",
+ authProvider, e);
+ }
// Do not allow misbehaving extensions to wipe out the
// UserContext entirely
@@ -181,13 +197,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
* The credentials associated with the request which produced the given
* UserContext.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If any of the given AuthenticationProviders fails while decorating
* the UserContext.
*/
public DecoratedUserContext(AuthenticationProvider authProvider,
UserContext userContext, AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
// Wrap the result of invoking decorate() on the given AuthenticationProvider
super(decorate(authProvider, userContext, authenticatedUser, credentials));
@@ -221,13 +237,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
* The credentials associated with the request which produced the given
* UserContext.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If any of the given AuthenticationProviders fails while decorating
* the UserContext.
*/
public DecoratedUserContext(AuthenticationProvider authProvider,
DecoratedUserContext userContext, AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
// Wrap the result of invoking decorate() on the given AuthenticationProvider
super(decorate(authProvider, userContext, authenticatedUser, credentials));
@@ -261,13 +277,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
* The credentials associated with the request which produced the given
* UserContext.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If any of the given AuthenticationProviders fails while decorating
* the UserContext.
*/
public DecoratedUserContext(DecoratedUserContext decorated,
UserContext userContext, AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
// Wrap the result of invoking redecorate() on the given AuthenticationProvider
super(redecorate(decorated, userContext, authenticatedUser, credentials));
@@ -303,13 +319,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
* The credentials associated with the request which produced the given
* UserContext.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If any of the given AuthenticationProviders fails while decorating
* the UserContext.
*/
public DecoratedUserContext(DecoratedUserContext decorated,
DecoratedUserContext userContext, AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
// Wrap the result of invoking redecorate() on the given AuthenticationProvider
super(redecorate(decorated, userContext, authenticatedUser, credentials));
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/DecorationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/DecorationService.java
index b28dc03af..0b7fc1264 100644
--- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/DecorationService.java
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/DecorationService.java
@@ -23,7 +23,6 @@ import java.util.Iterator;
import java.util.List;
import javax.inject.Inject;
-import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
@@ -65,12 +64,12 @@ public class DecorationService {
* A new DecoratedUserContext which has been decorated by all
* AuthenticationProviders.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If any AuthenticationProvider fails while decorating the UserContext.
*/
public DecoratedUserContext decorate(UserContext userContext,
AuthenticatedUser authenticatedUser, Credentials credentials)
- throws GuacamoleException {
+ throws GuacamoleAuthenticationProcessException {
// Get first AuthenticationProvider in list
Iterator current = authProviders.iterator();
@@ -119,12 +118,12 @@ public class DecorationService {
* A new DecoratedUserContext which has been decorated by all
* AuthenticationProviders.
*
- * @throws GuacamoleException
+ * @throws GuacamoleAuthenticationProcessException
* If any AuthenticationProvider fails while decorating the UserContext.
*/
public DecoratedUserContext redecorate(DecoratedUserContext decorated,
UserContext userContext, AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleAuthenticationProcessException {
// If the given DecoratedUserContext contains further decorated layers,
// redecorate those first
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/GuacamoleAuthenticationProcessException.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/GuacamoleAuthenticationProcessException.java
new file mode 100644
index 000000000..ba1d72a50
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/GuacamoleAuthenticationProcessException.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.rest.auth;
+
+import java.io.Serializable;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
+import org.apache.guacamole.protocol.GuacamoleStatus;
+
+/**
+ * An exception that occurs during Guacamole's authentication and authorization
+ * process, possibly associated with a specific AuthenticationProvider.
+ */
+public class GuacamoleAuthenticationProcessException extends GuacamoleException {
+
+ /**
+ * Internal identifier unique to this version of
+ * GuacamoleAuthenticationProcessException, as required by Java's
+ * {@link Serializable} interface.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The AuthenticationProvider that caused the failure, or null if there is
+ * no such specific AuthenticationProvider involved in this failure.
+ */
+ private final transient AuthenticationProvider authProvider;
+
+ /**
+ * A GuacamoleException representation of the failure that occurred. If
+ * the cause provided when this GuacamoleAuthenticationProcessException
+ * was created was a GuacamoleException, this will just be that exception.
+ * Otherwise, this will be a GuacamoleServerException wrapping the cause
+ * or a generic GuacamoleInvalidCredentialsException requesting a
+ * username/password if there is no specific cause at all.
+ */
+ private final GuacamoleException guacCause;
+
+ /**
+ * Converts the given Throwable to a GuacamoleException representing the
+ * failure that occurred. If the Throwable already is a GuacamoleException,
+ * this will just be that Throwable. For all other cases, a new
+ * GuacamoleException will be created that best represents the provided
+ * failure. If no failure is provided at all, a generic
+ * GuacamoleInvalidCredentialsException requesting a username/password is
+ * created.
+ *
+ * @param message
+ * A human-readable message describing the failure that occurred.
+ *
+ * @param cause
+ * The Throwable cause of the failure that occurred, if any, or null if
+ * the cause is not known to be a specific Throwable.
+ *
+ * @return
+ * A GuacamoleException representation of the message and cause
+ * provided.
+ */
+ private static GuacamoleException toGuacamoleException(String message,
+ Throwable cause) {
+
+ // Create generic invalid username/password exception if we have no
+ // specific cause
+ if (cause == null)
+ return new GuacamoleInvalidCredentialsException(
+ "Permission Denied.",
+ CredentialsInfo.USERNAME_PASSWORD
+ );
+
+ // If the specific cause is already a GuacamoleException, there's
+ // nothing for us to do here
+ if (cause instanceof GuacamoleException)
+ return (GuacamoleException) cause;
+
+ // Wrap all other Throwables as generic internal errors
+ return new GuacamoleServerException(message, cause);
+
+ }
+
+ /**
+ * Creates a new GuacamoleAuthenticationProcessException with the given
+ * message, associated AuthenticationProvider, and cause.
+ *
+ * @param message
+ * A human readable description of the exception that occurred.
+ *
+ * @param authProvider
+ * The AuthenticationProvider that caused the failure, or null if there
+ * is no such specific AuthenticationProvider involved in this failure.
+ *
+ * @param cause
+ * The cause of this exception, or null if the cause is unknown or
+ * there is no such cause.
+ */
+ public GuacamoleAuthenticationProcessException(String message,
+ AuthenticationProvider authProvider, Throwable cause) {
+ super(message, cause);
+ this.authProvider = authProvider;
+ this.guacCause = toGuacamoleException(message, cause);
+ }
+
+ /**
+ * Returns the AuthenticationProvider that caused the failure, if any. If
+ * there is no specific AuthenticationProvider involved in this failure,
+ * including if the failure is due to multiple AuthenticationProviders,
+ * this will be null.
+ *
+ * @return
+ * The AuthenticationProvider that caused the failure, or null if there
+ * is no such specific AuthenticationProvider involved in this failure.
+ */
+ public AuthenticationProvider getAuthenticationProvider() {
+ return authProvider;
+ }
+
+ /**
+ * Returns a GuacamoleException that represents the user-facing cause of
+ * this exception. A GuacamoleException will be returned by this function
+ * in all cases, including if no specific cause was given.
+ *
+ * @return
+ * A GuacamoleException that represents the user-facing cause of this
+ * exception.
+ */
+ public GuacamoleException getCauseAsGuacamoleException() {
+ return guacCause;
+ }
+
+ @Override
+ public GuacamoleStatus getStatus() {
+ return getCauseAsGuacamoleException().getStatus();
+ }
+
+ @Override
+ public int getHttpStatusCode() {
+ return getCauseAsGuacamoleException().getHttpStatusCode();
+ }
+
+ @Override
+ public int getWebSocketCode() {
+ return getCauseAsGuacamoleException().getWebSocketCode();
+ }
+
+}