From 685694aff4b0c2317eab64bc0104de925294931c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 10 May 2015 22:57:58 -0700 Subject: [PATCH] GUAC-587: Load authentication providers from extensions. --- .../net/basic/extension/Extension.java | 174 ++++++++++++++---- .../basic/extension/ExtensionManifest.java | 34 ++++ .../net/basic/extension/ExtensionModule.java | 9 + .../net/basic/rest/RESTAuthModule.java | 15 +- 4 files changed, 194 insertions(+), 38 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java index e97ae2543..61eb16c17 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java @@ -40,6 +40,7 @@ import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.ObjectMapper; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleServerException; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.basic.resource.ClassPathResource; import org.glyptodon.guacamole.net.basic.resource.Resource; @@ -74,6 +75,125 @@ public class Extension { */ private final ClassLoader classLoader; + /** + * The collection of all JavaScript resources defined within the extension. + */ + private final Collection javaScriptResources; + + /** + * The collection of all CSS resources defined within the extension. + */ + private final Collection cssResources; + + /** + * The collection of all AuthenticationProvider classes defined within the + * extension. + */ + private final Collection> authenticationProviderClasses; + + /** + * Returns a new collection of resources corresponding to the collection of + * paths provided. Each resource will be associated with the given + * mimetype. + * + * @param mimetype + * The mimetype to associate with each resource. + * + * @param paths + * The paths corresponding to the resources desired. + * + * @return + * A new, unmodifiable collection of resources corresponding to the + * collection of paths provided. + */ + private Collection getClassPathResources(String mimetype, Collection paths) { + + // If no paths are provided, just return an empty list + if (paths == null) + return Collections.emptyList(); + + // Add classpath resource for each path provided + Collection resources = new ArrayList(paths.size()); + for (String path : paths) + resources.add(new ClassPathResource(classLoader, mimetype, path)); + + // Callers should not rely on modifying the result + return Collections.unmodifiableCollection(resources); + + } + + /** + * Retrieve the AuthenticationProvider subclass having the given name. If + * the class having the given name does not exist or isn't actually a + * subclass of AuthenticationProvider, an exception will be thrown. + * + * @param name + * The name of the AuthenticationProvider class to retrieve. + * + * @return + * The subclass of AuthenticationProvider having the given name. + * + * @throws GuacamoleException + * If no such class exists, or if the class with the given name is not + * a subclass of AuthenticationProvider. + */ + @SuppressWarnings("unchecked") // We check this ourselves with isAssignableFrom() + private Class getAuthenticationProviderClass(String name) + throws GuacamoleException { + + try { + + // Get authentication provider class + Class authenticationProviderClass = classLoader.loadClass(name); + + // Verify the located class is actually a subclass of AuthenticationProvider + if (!AuthenticationProvider.class.isAssignableFrom(authenticationProviderClass)) + throw new GuacamoleServerException("Authentication providers MUST extend the AuthenticationProvider class."); + + // Return located class + return (Class) authenticationProviderClass; + + } + catch (ClassNotFoundException e) { + throw new GuacamoleException("Authentication provider class not found.", e); + } + + } + + /** + * Returns a new collection of all AuthenticationProvider subclasses having + * the given names. If any class does not exist or isn't actually a + * subclass of AuthenticationProvider, an exception will be thrown, and + * no further AuthenticationProvider classes will be loaded. + * + * @param names + * The names of the AuthenticationProvider classes to retrieve. + * + * @return + * A new collection of all AuthenticationProvider subclasses having the + * given names. + * + * @throws GuacamoleException + * If any given class does not exist, or if any given class is not a + * subclass of AuthenticationProvider. + */ + private Collection> getAuthenticationProviderClasses(Collection names) + throws GuacamoleException { + + // If no classnames are provided, just return an empty list + if (names == null) + return Collections.>emptyList(); + + // Define all auth provider classes + Collection> classes = new ArrayList>(names.size()); + for (String name : names) + classes.add(getAuthenticationProviderClass(name)); + + // Callers should not rely on modifying the result + return Collections.unmodifiableCollection(classes); + + } + /** * Loads the given file as an extension, which must be a .jar containing * a guac-manifest.json file describing its contents. @@ -160,6 +280,13 @@ public class Extension { throw new GuacamoleServerException("Unable to read extension: " + file.getName(), e); } + // Define static resources + cssResources = getClassPathResources("text/css", manifest.getCSSPaths()); + javaScriptResources = getClassPathResources("text/javascript", manifest.getJavaScriptPaths()); + + // Define authentication providers + authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders()); + } /** @@ -184,37 +311,6 @@ public class Extension { return manifest.getNamespace(); } - /** - * Returns a new collection of resources corresponding to the collection of - * paths provided. Each resource will be associated with the given - * mimetype. - * - * @param mimetype - * The mimetype to associate with each resource. - * - * @param paths - * The paths corresponding to the resources desired. - * - * @return - * A new, unmodifiable collection of resources corresponding to the - * collection of paths provided. - */ - private Collection getClassPathResources(String mimetype, Collection paths) { - - // If no paths are provided, just return an empty list - if (paths == null) - return Collections.emptyList(); - - // Add classpath resource for each path provided - Collection resources = new ArrayList(paths.size()); - for (String path : paths) - resources.add(new ClassPathResource(classLoader, mimetype, path)); - - // Callers should not rely on modifying the result - return Collections.unmodifiableCollection(resources); - - } - /** * Returns all declared JavaScript resources associated with this * extension. JavaScript resources are declared within the extension @@ -224,7 +320,7 @@ public class Extension { * All declared JavaScript resources associated with this extension. */ public Collection getJavaScriptResources() { - return getClassPathResources("text/javascript", manifest.getJavaScriptPaths()); + return javaScriptResources; } /** @@ -235,7 +331,19 @@ public class Extension { * All declared CSS resources associated with this extension. */ public Collection getCSSResources() { - return getClassPathResources("text/css", manifest.getCSSPaths()); + return cssResources; + } + + /** + * Returns all declared authentication providers classes associated with + * this extension. Authentication providers are declared within the + * extension manifest. + * + * @return + * All declared authentication provider classes with this extension. + */ + public Collection> getAuthenticationProviderClasses() { + return authenticationProviderClasses; } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java index 2e29b3d48..c593865d0 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java @@ -59,6 +59,12 @@ public class ExtensionManifest { */ private Collection cssPaths; + /** + * The names of all authentication provider classes within this extension, + * if any. + */ + private Collection authProviders; + /** * Returns the name of the extension associated with this manifest. The * name is human-readable, for display purposes only, and is defined within @@ -169,4 +175,32 @@ public class ExtensionManifest { this.cssPaths = cssPaths; } + /** + * Returns the classnames of all authentication provider classes within the + * extension. These classnames are defined within the manifest by the + * "authProviders" property as an array of strings, where each string is an + * authentication provider classname. + * + * @return + * A collection of classnames of all authentication providers within + * the extension. + */ + public Collection getAuthProviders() { + return authProviders; + } + + /** + * Sets the classnames of all authentication provider classes within the + * extension. These classnames are defined within the manifest by the + * "authProviders" property as an array of strings, where each string is an + * authentication provider classname. + * + * @param authProviders + * A collection of classnames of all authentication providers within + * the extension. + */ + public void setAuthProviders(Collection authProviders) { + this.authProviders = authProviders; + } + } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java index f6b2b011e..16aa55979 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java @@ -22,6 +22,7 @@ package org.glyptodon.guacamole.net.basic.extension; +import com.google.inject.Singleton; import com.google.inject.servlet.ServletModule; import java.io.File; import java.io.FileFilter; @@ -29,6 +30,7 @@ import java.util.ArrayList; import java.util.Collection; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.environment.Environment; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.basic.resource.Resource; import org.glyptodon.guacamole.net.basic.resource.ResourceServlet; import org.glyptodon.guacamole.net.basic.resource.SequenceResource; @@ -118,6 +120,13 @@ public class ExtensionModule extends ServletModule { javaScriptResources.addAll(extension.getJavaScriptResources()); cssResources.addAll(extension.getCSSResources()); + // Load all authentication providers as singletons + Collection> authenticationProviders = extension.getAuthenticationProviderClasses(); + for (Class authenticationProvider : authenticationProviders) { + logger.debug("Binding AuthenticationProvider \"{}\".", authenticationProvider); + bind(AuthenticationProvider.class).to(authenticationProvider).in(Singleton.class); + } + // Log successful loading of extension by name logger.info("Extension \"{}\" loaded.", extension.getName()); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java index d79746ad3..b6e5b3aee 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java @@ -84,15 +84,20 @@ public class RESTAuthModule extends AbstractModule { bind(AuthenticationService.class); bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class); - // Get and bind auth provider instance + // Get and bind auth provider instance, if defined via property try { - AuthenticationProvider authProvider = environment.getRequiredProperty(BasicGuacamoleProperties.AUTH_PROVIDER); - bind(AuthenticationProvider.class).toInstance(authProvider); + + // Use "auth-provider" property if present, but warn about deprecation + AuthenticationProvider authProvider = environment.getProperty(BasicGuacamoleProperties.AUTH_PROVIDER); + if (authProvider != null) { + logger.warn("The \"auth-provider\" is now deprecated. Please use the \"extensions\" directory within GUACAMOLE_HOME instead."); + bind(AuthenticationProvider.class).toInstance(authProvider); + } + } catch (GuacamoleException e) { - logger.error("Unable to read authentication provider from guacamole.properties: {}", e.getMessage()); + logger.warn("Value of deprecated \"auth-provider\" property within guacamole.properties is not valid: {}", e.getMessage()); logger.debug("Error reading authentication provider from guacamole.properties.", e); - throw new RuntimeException(e); } }