Repository: guacamole-client
Updated Branches:
  refs/heads/master 7d822df5a -> 81010a8b6


GUACAMOLE-611: Allow authentication providers to be explicitly skipped if 
internal errors occur.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/57831441
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/57831441
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/57831441

Branch: refs/heads/master
Commit: 57831441ed4f8b285c19075d7ab7d84d6fd22812
Parents: a34bbcf
Author: Michael Jumper <mjum...@apache.org>
Authored: Sat Aug 25 13:19:17 2018 -0700
Committer: Michael Jumper <mjum...@apache.org>
Committed: Sat Aug 25 13:19:17 2018 -0700

----------------------------------------------------------------------
 .../extension/AuthenticationProviderFacade.java | 145 +++++++++++++++++--
 .../guacamole/extension/ExtensionModule.java    | 106 +++++++++++++-
 2 files changed, 234 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/57831441/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java
----------------------------------------------------------------------
diff --git 
a/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java
 
b/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java
index a868931..ecb0a40 100644
--- 
a/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java
+++ 
b/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java
@@ -19,14 +19,14 @@
 
 package org.apache.guacamole.extension;
 
+import java.util.Set;
 import java.util.UUID;
 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;
 import org.apache.guacamole.net.auth.UserContext;
-import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
-import 
org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
+import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,6 +49,16 @@ public class AuthenticationProviderFacade implements 
AuthenticationProvider {
     private final AuthenticationProvider authProvider;
 
     /**
+     * The set of identifiers of all authentication providers whose internal
+     * failures should be tolerated during the authentication process. If the
+     * identifier of this authentication provider is within this set, errors
+     * during authentication will result in the authentication provider being
+     * ignored for that authentication attempt. By default, errors during
+     * authentication halt the authentication process entirely.
+     */
+    private final Set<String> tolerateFailures;
+
+    /**
      * The identifier to provide for the underlying authentication provider if
      * the authentication provider could not be loaded.
      */
@@ -63,9 +73,21 @@ public class AuthenticationProviderFacade implements 
AuthenticationProvider {
      *
      * @param authProviderClass
      *     The AuthenticationProvider subclass to instantiate.
+     *
+     * @param tolerateFailures
+     *     The set of identifiers of all authentication providers whose
+     *     internal failures should be tolerated during the authentication
+     *     process. If the identifier of this authentication provider is within
+     *     this set, errors during authentication will result in the
+     *     authentication provider being ignored for that authentication
+     *     attempt. By default, errors during authentication halt the
+     *     authentication process entirely.
      */
-    public AuthenticationProviderFacade(Class<? extends 
AuthenticationProvider> authProviderClass) {
-        authProvider = ProviderFactory.newInstance("authentication provider",
+    public AuthenticationProviderFacade(
+            Class<? extends AuthenticationProvider> authProviderClass,
+            Set<String> tolerateFailures) {
+        this.tolerateFailures = tolerateFailures;
+        this.authProvider = ProviderFactory.newInstance("authentication 
provider",
             authProviderClass);
     }
 
@@ -97,6 +119,41 @@ public class AuthenticationProviderFacade implements 
AuthenticationProvider {
 
     }
 
+    /**
+     * Returns whether this authentication provider should tolerate internal
+     * failures during the authentication process, allowing other
+     * authentication providers to continue operating as if this authentication
+     * provider simply is not present.
+     *
+     * @return
+     *     true if this authentication provider should tolerate internal
+     *     failures during the authentication process, false otherwise.
+     */
+    private boolean isFailureTolerated() {
+        return tolerateFailures.contains(getIdentifier());
+    }
+
+    /**
+     * Logs a warning that this authentication provider is being skipped due to
+     * an internal error. If debug-level logging is enabled, the full details
+     * of the internal error are also logged.
+     *
+     * @param e
+     *     The internal error that occurred which has resulted in this
+     *     authentication provider being skipped.
+     */
+    private void warnAuthProviderSkipped(Throwable e) {
+
+        logger.warn("The \"{}\" authentication provider has been skipped due "
+                + "to an internal error. If this is unexpected or you are the "
+                + "developer of this authentication provider, you may wish to "
+                + "enable debug-level logging: {}",
+                getIdentifier(), e.getMessage());
+
+        logger.debug("Authentication provider skipped due to an internal 
failure.", e);
+
+    }
+
     @Override
     public AuthenticatedUser authenticateUser(Credentials credentials)
             throws GuacamoleException {
@@ -104,11 +161,46 @@ public class AuthenticationProviderFacade implements 
AuthenticationProvider {
         // Ignore auth attempts if no auth provider could be loaded
         if (authProvider == null) {
             logger.warn("Authentication attempt denied because the 
authentication system could not be loaded. Please check for errors earlier in 
the logs.");
-            throw new GuacamoleInvalidCredentialsException("Permission 
denied.", CredentialsInfo.USERNAME_PASSWORD);
+            return null;
         }
 
         // Delegate to underlying auth provider
-        return authProvider.authenticateUser(credentials);
+        try {
+            return authProvider.authenticateUser(credentials);
+        }
+
+        // Pass through credential exceptions untouched, as these are not
+        // internal failures
+        catch (GuacamoleCredentialsException e) {
+            throw e;
+        }
+
+        // Pass through all other exceptions (aborting authentication entirely)
+        // only if not configured to ignore such failures
+        catch (GuacamoleException e) {
+
+            // Skip using this authentication provider if configured to ignore
+            // internal failures during auth
+            if (isFailureTolerated()) {
+                warnAuthProviderSkipped(e);
+                return null;
+            }
+
+            throw e;
+
+        }
+        catch (RuntimeException e) {
+
+            // Skip using this authentication provider if configured to ignore
+            // internal failures during auth
+            if (isFailureTolerated()) {
+                warnAuthProviderSkipped(e);
+                return null;
+            }
+
+            throw e;
+
+        }
 
     }
 
@@ -119,7 +211,7 @@ public class AuthenticationProviderFacade implements 
AuthenticationProvider {
         // Ignore auth attempts if no auth provider could be loaded
         if (authProvider == null) {
             logger.warn("Reauthentication attempt denied because the 
authentication system could not be loaded. Please check for errors earlier in 
the logs.");
-            throw new GuacamoleInvalidCredentialsException("Permission 
denied.", CredentialsInfo.USERNAME_PASSWORD);
+            return null;
         }
 
         // Delegate to underlying auth provider
@@ -138,8 +230,43 @@ public class AuthenticationProviderFacade implements 
AuthenticationProvider {
         }
 
         // Delegate to underlying auth provider
-        return authProvider.getUserContext(authenticatedUser);
-        
+        try {
+            return authProvider.getUserContext(authenticatedUser);
+        }
+
+        // Pass through credential exceptions untouched, as these are not
+        // internal failures
+        catch (GuacamoleCredentialsException e) {
+            throw e;
+        }
+
+        // Pass through all other exceptions (aborting authentication entirely)
+        // only if not configured to ignore such failures
+        catch (GuacamoleException e) {
+
+            // Skip using this authentication provider if configured to ignore
+            // internal failures during auth
+            if (isFailureTolerated()) {
+                warnAuthProviderSkipped(e);
+                return null;
+            }
+
+            throw e;
+
+        }
+        catch (RuntimeException e) {
+
+            // Skip using this authentication provider if configured to ignore
+            // internal failures during auth
+            if (isFailureTolerated()) {
+                warnAuthProviderSkipped(e);
+                return null;
+            }
+
+            throw e;
+
+        }
+
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/57831441/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
----------------------------------------------------------------------
diff --git 
a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java 
b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
index cc39036..c276cc1 100644
--- 
a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
+++ 
b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
@@ -29,12 +29,14 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import org.apache.guacamole.auth.file.FileAuthenticationProvider;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.GuacamoleServerException;
 import org.apache.guacamole.environment.Environment;
 import org.apache.guacamole.net.auth.AuthenticationProvider;
 import org.apache.guacamole.net.event.listener.Listener;
+import org.apache.guacamole.properties.StringSetProperty;
 import org.apache.guacamole.resource.Resource;
 import org.apache.guacamole.resource.ResourceServlet;
 import org.apache.guacamole.resource.SequenceResource;
@@ -82,6 +84,25 @@ public class ExtensionModule extends ServletModule {
     private static final String EXTENSION_SUFFIX = ".jar";
 
     /**
+     * A comma-separated list of the identifiers of all authentication
+     * providers whose internal failures should be tolerated during the
+     * authentication process. If an authentication provider within this list
+     * encounters an internal error during the authentication process, it will
+     * simply be skipped, allowing other authentication providers to continue
+     * trying to authenticate the user. Internal errors within authentication
+     * providers that are not within this list will halt the authentication
+     * process entirely.
+     */
+    private final StringSetProperty TOLERATE_INTERNAL_FAILURES = new 
StringSetProperty() {
+
+        @Override
+        public String getName() {
+            return "tolerate-internal-failures";
+        }
+
+    };
+
+    /**
      * The Guacamole server environment.
      */
     private final Environment environment;
@@ -156,13 +177,26 @@ public class ExtensionModule extends ServletModule {
      *
      * @param authenticationProvider
      *     The AuthenticationProvider class to bind.
+     *
+     * @param tolerateFailures
+     *     The set of identifiers of all authentication providers whose
+     *     internal failures should be tolerated during the authentication
+     *     process. If the identifier of an authentication provider is within
+     *     this set, errors during authentication will result in the
+     *     authentication provider being ignored for that authentication
+     *     attempt, with the authentication process proceeding as if that
+     *     authentication provider were not present. By default, errors during
+     *     authentication halt the authentication process entirely.
      */
-    private void bindAuthenticationProvider(Class<? extends 
AuthenticationProvider> authenticationProvider) {
+    private void bindAuthenticationProvider(
+            Class<? extends AuthenticationProvider> authenticationProvider,
+            Set<String> tolerateFailures) {
 
         // Bind authentication provider
         logger.debug("[{}] Binding AuthenticationProvider \"{}\".",
                 boundAuthenticationProviders.size(), 
authenticationProvider.getName());
-        boundAuthenticationProviders.add(new 
AuthenticationProviderFacade(authenticationProvider));
+        boundAuthenticationProviders.add(new AuthenticationProviderFacade(
+                authenticationProvider, tolerateFailures));
 
     }
 
@@ -173,12 +207,24 @@ public class ExtensionModule extends ServletModule {
      *
      * @param authProviders
      *     The AuthenticationProvider classes to bind.
+     *
+     * @param tolerateFailures
+     *     The set of identifiers of all authentication providers whose
+     *     internal failures should be tolerated during the authentication
+     *     process. If the identifier of an authentication provider is within
+     *     this set, errors during authentication will result in the
+     *     authentication provider being ignored for that authentication
+     *     attempt, with the authentication process proceeding as if that
+     *     authentication provider were not present. By default, errors during
+     *     authentication halt the authentication process entirely.
      */
-    private void 
bindAuthenticationProviders(Collection<Class<AuthenticationProvider>> 
authProviders) {
+    private void bindAuthenticationProviders(
+            Collection<Class<AuthenticationProvider>> authProviders,
+            Set<String> tolerateFailures) {
 
         // Bind each authentication provider within extension
         for (Class<AuthenticationProvider> authenticationProvider : 
authProviders)
-            bindAuthenticationProvider(authenticationProvider);
+            bindAuthenticationProvider(authenticationProvider, 
tolerateFailures);
 
     }
 
@@ -314,6 +360,38 @@ public class ExtensionModule extends ServletModule {
     }
 
     /**
+     * Returns the set of identifiers of all authentication providers whose
+     * internal failures should be tolerated during the authentication process.
+     * If the identifier of an authentication provider is within this set,
+     * errors during authentication will result in the authentication provider
+     * being ignored for that authentication attempt, with the authentication
+     * process proceeding as if that authentication provider were not present.
+     * By default, errors during authentication halt the authentication process
+     * entirely.
+     *
+     * @return
+     *     The set of identifiers of all authentication providers whose
+     *     internal failures should be tolerated during the authentication
+     *     process.
+     */
+    private Set<String> getToleratedAuthenticationProviders() {
+
+        // Parse list of auth providers whose internal failures should be
+        // tolerated
+        try {
+            return environment.getProperty(TOLERATE_INTERNAL_FAILURES, 
Collections.<String>emptySet());
+        }
+
+        // Use empty set by default if property cannot be parsed
+        catch (GuacamoleException e) {
+            logger.warn("The list of authentication providers specified via 
the \"{}\" property could not be parsed: {}", 
TOLERATE_INTERNAL_FAILURES.getName(), e.getMessage());
+            logger.debug("Unable to parse \"{}\" property.", 
TOLERATE_INTERNAL_FAILURES.getName(), e);
+            return Collections.<String>emptySet();
+        }
+
+    }
+
+    /**
      * Loads all extensions within the GUACAMOLE_HOME/extensions directory, if
      * any, adding their static resource to the given resoure collections.
      *
@@ -324,9 +402,20 @@ public class ExtensionModule extends ServletModule {
      * @param cssResources
      *     A modifiable collection of static CSS resources which may receive
      *     new CSS resources from extensions.
+     *
+     * @param toleratedAuthProviders
+     *     The set of identifiers of all authentication providers whose
+     *     internal failures should be tolerated during the authentication
+     *     process. If the identifier of an authentication provider is within
+     *     this set, errors during authentication will result in the
+     *     authentication provider being ignored for that authentication
+     *     attempt, with the authentication process proceeding as if that
+     *     authentication provider were not present. By default, errors during
+     *     authentication halt the authentication process entirely.
      */
     private void loadExtensions(Collection<Resource> javaScriptResources,
-            Collection<Resource> cssResources) {
+            Collection<Resource> cssResources,
+            Set<String> toleratedAuthProviders) {
 
         // Retrieve and validate extensions directory
         File extensionsDir = new File(environment.getGuacamoleHome(), 
EXTENSIONS_DIRECTORY);
@@ -375,7 +464,7 @@ public class ExtensionModule extends ServletModule {
                 cssResources.addAll(extension.getCSSResources().values());
 
                 // Attempt to load all authentication providers
-                
bindAuthenticationProviders(extension.getAuthenticationProviderClasses());
+                
bindAuthenticationProviders(extension.getAuthenticationProviderClasses(), 
toleratedAuthProviders);
 
                 // Attempt to load all listeners
                 bindListeners(extension.getListenerClasses());
@@ -430,10 +519,11 @@ public class ExtensionModule extends ServletModule {
         cssResources.add(new WebApplicationResource(getServletContext(), 
"/guacamole.min.css"));
 
         // Load all extensions
-        loadExtensions(javaScriptResources, cssResources);
+        final Set<String> toleratedAuthProviders = 
getToleratedAuthenticationProviders();
+        loadExtensions(javaScriptResources, cssResources, 
toleratedAuthProviders);
 
         // Always bind default file-driven auth last
-        bindAuthenticationProvider(FileAuthenticationProvider.class);
+        bindAuthenticationProvider(FileAuthenticationProvider.class, 
toleratedAuthProviders);
 
         // Dynamically generate app.js and app.css from extensions
         serve("/app.js").with(new ResourceServlet(new 
SequenceResource(javaScriptResources)));

Reply via email to