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/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 9aa26fafc..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.getDelegateUserContext(), - 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(); + } + +}