From 9419356433f76598d93d6c56c9769729b9d69f19 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 9 May 2015 23:58:36 -0700 Subject: [PATCH 01/22] GUAC-587: Serve JavaScript and CSS through app.js and app.css respectively. --- .../guacamole/net/basic/rest/RESTServletModule.java | 6 ++++++ guacamole/src/main/webapp/index.html | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java index a6e54c7a6..34bfb0790 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java @@ -27,6 +27,8 @@ import com.google.inject.matcher.Matchers; import com.google.inject.servlet.ServletModule; import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; import org.codehaus.jackson.jaxrs.JacksonJsonProvider; +import org.glyptodon.guacamole.net.basic.resource.ResourceServlet; +import org.glyptodon.guacamole.net.basic.resource.WebApplicationResource; import org.glyptodon.guacamole.net.basic.rest.auth.TokenRESTService; import org.glyptodon.guacamole.net.basic.rest.clipboard.ClipboardRESTService; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService; @@ -71,6 +73,10 @@ public class RESTServletModule extends ServletModule { bind(JacksonJsonProvider.class).in(Scopes.SINGLETON); serve("/api/*").with(GuiceContainer.class); + // TODO: Pull these from extensions, dynamically concatenated + serve("/app.js").with(new ResourceServlet(new WebApplicationResource(getServletContext(), "/guacamole.min.js"))); + serve("/app.css").with(new ResourceServlet(new WebApplicationResource(getServletContext(), "/guacamole.min.css"))); + } } diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index eb6d28dad..3303ec1a8 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -8,7 +8,7 @@ - + - + From 6b4e798c6c6ae3a4a3998383a7054f63dd3c8921 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 10 May 2015 00:54:09 -0700 Subject: [PATCH 02/22] GUAC-587: Use ExtensionModule to load extensions and set up app.css / app.js. --- .../basic/BasicServletContextListener.java | 2 + .../net/basic/extension/ExtensionModule.java | 105 ++++++++++++++++++ .../net/basic/extension/package-info.java | 27 +++++ .../net/basic/rest/RESTServletModule.java | 6 - 4 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/package-info.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java index 82f3983de..e6f342f2c 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java @@ -30,6 +30,7 @@ import javax.servlet.ServletContextEvent; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.environment.LocalEnvironment; +import org.glyptodon.guacamole.net.basic.extension.ExtensionModule; import org.glyptodon.guacamole.net.basic.log.LogModule; import org.glyptodon.guacamole.net.basic.rest.RESTAuthModule; import org.glyptodon.guacamole.net.basic.rest.RESTServletModule; @@ -83,6 +84,7 @@ public class BasicServletContextListener extends GuiceServletContextListener { return Guice.createInjector(Stage.PRODUCTION, new EnvironmentModule(environment), new LogModule(environment), + new ExtensionModule(environment), new RESTAuthModule(environment, sessionMap), new RESTServletModule(), new TunnelModule() 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 new file mode 100644 index 000000000..60502c542 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.extension; + +import com.google.inject.servlet.ServletModule; +import java.io.File; +import java.io.FileFilter; +import org.glyptodon.guacamole.environment.Environment; +import org.glyptodon.guacamole.net.basic.resource.ResourceServlet; +import org.glyptodon.guacamole.net.basic.resource.WebApplicationResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Guice Module which loads all extensions within the + * GUACAMOLE_HOME/extensions directory, if any. + * + * @author Michael Jumper + */ +public class ExtensionModule extends ServletModule { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class); + + /** + * The name of the directory within GUACAMOLE_HOME containing all + * extensions. + */ + private static final String EXTENSIONS_DIRECTORY = "extensions"; + + /** + * The string that the filenames of all extensions must end with to be + * recognized as extensions. + */ + private static final String EXTENSION_SUFFIX = ".jar"; + + /** + * The Guacamole server environment. + */ + private final Environment environment; + + /** + * Creates a module which loads all extensions within the + * GUACAMOLE_HOME/extensions directory. + * + * @param environment + * The environment to use when configuring authentication. + */ + public ExtensionModule(Environment environment) { + this.environment = environment; + } + + @Override + protected void configureServlets() { + + // Retrieve and validate extensions directory + File extensionsDir = new File(environment.getGuacamoleHome(), EXTENSIONS_DIRECTORY); + if (!extensionsDir.isDirectory()) + return; + + // Retrieve list of all extension files within extensions directory + File[] extensions = extensionsDir.listFiles(new FileFilter() { + + @Override + public boolean accept(File file) { + return file.isFile() && file.getName().endsWith(EXTENSION_SUFFIX); + } + + }); + + // Load each extension + for (File extension : extensions) { + // TODO: Actually load extension + logger.info("Loading extension: \"{}\"", extension.getName()); + } + + // TODO: Pull these from extensions, dynamically concatenated + serve("/app.js").with(new ResourceServlet(new WebApplicationResource(getServletContext(), "/guacamole.min.js"))); + serve("/app.css").with(new ResourceServlet(new WebApplicationResource(getServletContext(), "/guacamole.min.css"))); + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/package-info.java new file mode 100644 index 000000000..198e4c4b7 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/package-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Classes which represent and facilitate the loading of extensions to the + * Guacamole web application. + */ +package org.glyptodon.guacamole.net.basic.extension; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java index 34bfb0790..a6e54c7a6 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java @@ -27,8 +27,6 @@ import com.google.inject.matcher.Matchers; import com.google.inject.servlet.ServletModule; import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; import org.codehaus.jackson.jaxrs.JacksonJsonProvider; -import org.glyptodon.guacamole.net.basic.resource.ResourceServlet; -import org.glyptodon.guacamole.net.basic.resource.WebApplicationResource; import org.glyptodon.guacamole.net.basic.rest.auth.TokenRESTService; import org.glyptodon.guacamole.net.basic.rest.clipboard.ClipboardRESTService; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService; @@ -73,10 +71,6 @@ public class RESTServletModule extends ServletModule { bind(JacksonJsonProvider.class).in(Scopes.SINGLETON); serve("/api/*").with(GuiceContainer.class); - // TODO: Pull these from extensions, dynamically concatenated - serve("/app.js").with(new ResourceServlet(new WebApplicationResource(getServletContext(), "/guacamole.min.js"))); - serve("/app.css").with(new ResourceServlet(new WebApplicationResource(getServletContext(), "/guacamole.min.css"))); - } } From 8ae9a2e28c30b3b21cd40c929535d6f626db886f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 10 May 2015 21:10:56 -0700 Subject: [PATCH 03/22] GUAC-587: Define class representing extensions and the extension manifest file. Dynamically add JavaScript and CSS resources to app.js and app.css. --- .../net/basic/extension/Extension.java | 241 ++++++++++++++++++ .../basic/extension/ExtensionManifest.java | 172 +++++++++++++ .../net/basic/extension/ExtensionModule.java | 53 +++- 3 files changed, 456 insertions(+), 10 deletions(-) create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java 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 new file mode 100644 index 000000000..e97ae2543 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/Extension.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.extension; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +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.basic.resource.ClassPathResource; +import org.glyptodon.guacamole.net.basic.resource.Resource; + +/** + * A Guacamole extension, which may provide custom authentication, static + * files, theming/branding, etc. + * + * @author Michael Jumper + */ +public class Extension { + + /** + * The Jackson parser for parsing the language JSON files. + */ + private static final ObjectMapper mapper = new ObjectMapper(); + + /** + * The name of the manifest file that describes the contents of a + * Guacamole extension. + */ + private static final String MANIFEST_NAME = "guac-manifest.json"; + + /** + * The parsed manifest file of this extension, describing the location of + * resources within the extension. + */ + private final ExtensionManifest manifest; + + /** + * The classloader to use when reading resources from this extension, + * including classes and static files. + */ + private final ClassLoader classLoader; + + /** + * Loads the given file as an extension, which must be a .jar containing + * a guac-manifest.json file describing its contents. + * + * @param parent + * The classloader to use as the parent for the isolated classloader of + * this extension. + * + * @param file + * The file to load as an extension. + * + * @throws GuacamoleException + * If the provided file is not a .jar file, does not contain the + * guac-manifest.json, or if guac-manifest.json is invalid and cannot + * be parsed. + */ + public Extension(final ClassLoader parent, final File file) throws GuacamoleException { + + try { + + // Open extension + ZipFile extension = new ZipFile(file); + + try { + + // Retrieve extension manifest + ZipEntry manifestEntry = extension.getEntry(MANIFEST_NAME); + if (manifestEntry == null) + throw new GuacamoleServerException("Extension " + file.getName() + " is missing " + MANIFEST_NAME); + + // Parse manifest + manifest = mapper.readValue(extension.getInputStream(manifestEntry), ExtensionManifest.class); + + } + + // Always close zip file, if possible + finally { + extension.close(); + } + + try { + + // Create isolated classloader for this extension + classLoader = AccessController.doPrivileged(new PrivilegedExceptionAction() { + + @Override + public ClassLoader run() throws GuacamoleException { + + try { + + // Classloader must contain only the extension itself + return new URLClassLoader(new URL[]{file.toURI().toURL()}, parent); + + } + catch (MalformedURLException e) { + throw new GuacamoleException(e); + } + + } + + }); + + } + + // Rethrow any GuacamoleException + catch (PrivilegedActionException e) { + throw (GuacamoleException) e.getException(); + } + + } + + // Abort load if not a valid zip file + catch (ZipException e) { + throw new GuacamoleServerException("Extension is not a valid zip file: " + file.getName(), e); + } + + // Abort if manifest cannot be parsed (invalid JSON) + catch (JsonParseException e) { + throw new GuacamoleServerException(MANIFEST_NAME + " is not valid JSON: " + file.getName(), e); + } + + // Abort if zip file cannot be read at all due to I/O errors + catch (IOException e) { + throw new GuacamoleServerException("Unable to read extension: " + file.getName(), e); + } + + } + + /** + * Returns the name of this extension, as declared in the extension's + * manifest. + * + * @return + * The name of this extension. + */ + public String getName() { + return manifest.getName(); + } + + /** + * Returns the namespace of this extension, as declared in the extension's + * manifest. + * + * @return + * The namespace of this extension. + */ + public String getNamespace() { + 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 + * manifest. + * + * @return + * All declared JavaScript resources associated with this extension. + */ + public Collection getJavaScriptResources() { + return getClassPathResources("text/javascript", manifest.getJavaScriptPaths()); + } + + /** + * Returns all declared CSS resources associated with this extension. CSS + * resources are declared within the extension manifest. + * + * @return + * All declared CSS resources associated with this extension. + */ + public Collection getCSSResources() { + return getClassPathResources("text/css", manifest.getCSSPaths()); + } + +} 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 new file mode 100644 index 000000000..2e29b3d48 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionManifest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.extension; + +import java.util.Collection; +import org.codehaus.jackson.annotate.JsonProperty; + +/** + * Java representation of the JSON manifest contained within every Guacamole + * extension, identifying an extension and describing its contents. + * + * @author Michael Jumper + */ +public class ExtensionManifest { + + /** + * The name of the extension associated with this manifest. The extension + * name is human-readable, and used for display purposes only. + */ + private String name; + + /** + * The namespace of the extension associated with this manifest. The + * extension namespace is required for internal use, and is used wherever + * extension-specific files or resources need to be isolated from those of + * other extensions. + */ + private String namespace; + + /** + * The paths of all JavaScript resources within the .jar of the extension + * associated with this manifest. + */ + private Collection javaScriptPaths; + + /** + * The paths of all CSS resources within the .jar of the extension + * associated with this manifest. + */ + private Collection cssPaths; + + /** + * Returns the name of the extension associated with this manifest. The + * name is human-readable, for display purposes only, and is defined within + * the manifest by the "name" property. + * + * @return + * The name of the extension associated with this manifest. + */ + public String getName() { + return name; + } + + /** + * Sets the name of the extension associated with this manifest. The name + * is human-readable, for display purposes only, and is defined within the + * manifest by the "name" property. + * + * @param name + * The name of the extension associated with this manifest. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the namespace of the extension associated with this manifest. + * The namespace is required for internal use, and is used wherever + * extension-specific files or resources need to be isolated from those of + * other extensions. It is defined within the manifest by the "namespace" + * property. + * + * @return + * The namespace of the extension associated with this manifest. + */ + public String getNamespace() { + return namespace; + } + + /** + * Sets the namespace of the extension associated with this manifest. The + * namespace is required for internal use, and is used wherever extension- + * specific files or resources need to be isolated from those of other + * extensions. It is defined within the manifest by the "namespace" + * property. + * + * @param namespace + * The namespace of the extension associated with this manifest. + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * Returns the paths to all JavaScript resources within the extension. + * These paths are defined within the manifest by the "js" property as an + * array of strings, where each string is a path relative to the root of + * the extension .jar. + * + * @return + * A collection of paths to all JavaScript resources within the + * extension. + */ + @JsonProperty("js") + public Collection getJavaScriptPaths() { + return javaScriptPaths; + } + + /** + * Sets the paths to all JavaScript resources within the extension. These + * paths are defined within the manifest by the "js" property as an array + * of strings, where each string is a path relative to the root of the + * extension .jar. + * + * @param javaScriptPaths + * A collection of paths to all JavaScript resources within the + * extension. + */ + @JsonProperty("js") + public void setJavaScriptPaths(Collection javaScriptPaths) { + this.javaScriptPaths = javaScriptPaths; + } + + /** + * Returns the paths to all CSS resources within the extension. These paths + * are defined within the manifest by the "js" property as an array of + * strings, where each string is a path relative to the root of the + * extension .jar. + * + * @return + * A collection of paths to all CSS resources within the extension. + */ + @JsonProperty("css") + public Collection getCSSPaths() { + return cssPaths; + } + + /** + * Sets the paths to all CSS resources within the extension. These paths + * are defined within the manifest by the "js" property as an array of + * strings, where each string is a path relative to the root of the + * extension .jar. + * + * @param cssPaths + * A collection of paths to all CSS resources within the extension. + */ + @JsonProperty("css") + public void setCSSPaths(Collection cssPaths) { + this.cssPaths = cssPaths; + } + +} 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 60502c542..f6b2b011e 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 @@ -25,8 +25,13 @@ package org.glyptodon.guacamole.net.basic.extension; import com.google.inject.servlet.ServletModule; import java.io.File; import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collection; +import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.environment.Environment; +import org.glyptodon.guacamole.net.basic.resource.Resource; import org.glyptodon.guacamole.net.basic.resource.ResourceServlet; +import org.glyptodon.guacamole.net.basic.resource.SequenceResource; import org.glyptodon.guacamole.net.basic.resource.WebApplicationResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +60,7 @@ public class ExtensionModule extends ServletModule { * recognized as extensions. */ private static final String EXTENSION_SUFFIX = ".jar"; - + /** * The Guacamole server environment. */ @@ -81,7 +86,7 @@ public class ExtensionModule extends ServletModule { return; // Retrieve list of all extension files within extensions directory - File[] extensions = extensionsDir.listFiles(new FileFilter() { + File[] extensionFiles = extensionsDir.listFiles(new FileFilter() { @Override public boolean accept(File file) { @@ -90,15 +95,43 @@ public class ExtensionModule extends ServletModule { }); - // Load each extension - for (File extension : extensions) { - // TODO: Actually load extension - logger.info("Loading extension: \"{}\"", extension.getName()); + // Init JavaScript resources with base guacamole.min.js + Collection javaScriptResources = new ArrayList(); + javaScriptResources.add(new WebApplicationResource(getServletContext(), "/guacamole.min.js")); + + // Init CSS resources with base guacamole.min.css + Collection cssResources = new ArrayList(); + cssResources.add(new WebApplicationResource(getServletContext(), "/guacamole.min.css")); + + // Load each extension within the extension directory + for (File extensionFile : extensionFiles) { + + logger.debug("Loading extension: \"{}\"", extensionFile.getName()); + + try { + + // FIXME: Use class loader which reads from the lib directory + // Load extension from file + Extension extension = new Extension(ExtensionModule.class.getClassLoader(), extensionFile); + + // Add any JavaScript / CSS resources + javaScriptResources.addAll(extension.getJavaScriptResources()); + cssResources.addAll(extension.getCSSResources()); + + // Log successful loading of extension by name + logger.info("Extension \"{}\" loaded.", extension.getName()); + + } + catch (GuacamoleException e) { + logger.error("Extension \"{}\" could not be loaded: {}", extensionFile.getName(), e.getMessage()); + logger.debug("Unable to load extension.", e); + } + } - - // TODO: Pull these from extensions, dynamically concatenated - serve("/app.js").with(new ResourceServlet(new WebApplicationResource(getServletContext(), "/guacamole.min.js"))); - serve("/app.css").with(new ResourceServlet(new WebApplicationResource(getServletContext(), "/guacamole.min.css"))); + + // Dynamically generate app.js and app.css from extensions + serve("/app.js").with(new ResourceServlet(new SequenceResource(javaScriptResources))); + serve("/app.css").with(new ResourceServlet(new SequenceResource(cssResources))); } From 685694aff4b0c2317eab64bc0104de925294931c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 10 May 2015 22:57:58 -0700 Subject: [PATCH 04/22] 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); } } From ba639f9e505e1532f532964a06d7afb4e2d4c524 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 10 May 2015 22:58:16 -0700 Subject: [PATCH 05/22] GUAC-587: Add guac-manifest.json to JDBC auth implementations. --- .../src/main/resources/guac-manifest.json | 11 +++++++++++ .../src/main/resources/guac-manifest.json | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..d9aad44f3 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json @@ -0,0 +1,11 @@ +{ + + "name" : "MySQL Authentication", + "namespace" : "guac-mysql", + + "authProviders" : [ + "net.sourceforge.guacamole.net.auth.mysql.MySQLAuthenticationProvider" + ] + +} + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..59b5ed7ca --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json @@ -0,0 +1,11 @@ +{ + + "name" : "PostgreSQL Authentication", + "namespace" : "guac-postgresql", + + "authProviders" : [ + "org.glyptodon.guacamole.auth.postgresql.PostgreSQLAuthenticationProvider" + ] + +} + From 815a190fd4a767557c6ea4bc62e7dc92ee446845 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 10 May 2015 23:18:01 -0700 Subject: [PATCH 06/22] GUAC-587: Remove unnecessary use of GuacamoleClassLoader. --- .../glyptodon/guacamole/net/basic/TunnelModule.java | 9 +-------- .../net/basic/websocket/WebSocketTunnelModule.java | 10 +--------- .../basic/websocket/jetty8/WebSocketTunnelModule.java | 10 +--------- .../basic/websocket/jetty9/WebSocketTunnelModule.java | 10 +--------- .../basic/websocket/tomcat/WebSocketTunnelModule.java | 10 +--------- 5 files changed, 5 insertions(+), 44 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java index 4df334947..9ee464628 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java @@ -24,7 +24,6 @@ package org.glyptodon.guacamole.net.basic; import com.google.inject.servlet.ServletModule; import java.lang.reflect.InvocationTargetException; -import org.glyptodon.guacamole.GuacamoleException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +54,7 @@ public class TunnelModule extends ServletModule { try { // Attempt to find WebSocket module - Class module = (Class) GuacamoleClassLoader.getInstance().findClass(classname); + Class module = Class.forName(classname); // Create loader TunnelLoader loader = (TunnelLoader) module.getConstructor().newInstance(); @@ -85,12 +84,6 @@ public class TunnelModule extends ServletModule { logger.debug("Error instantiating WebSocket module.", e); } - // Log all GuacamoleExceptions - catch (GuacamoleException e) { - logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); - logger.debug("Error loading/detecting WebSocket support.", e); - } - // Load attempt failed return false; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java index 1a42bc2fb..69a62fabe 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java @@ -28,8 +28,6 @@ import java.util.Arrays; import javax.websocket.DeploymentException; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpointConfig; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; import org.glyptodon.guacamole.net.basic.TunnelLoader; import org.glyptodon.guacamole.net.basic.TunnelRequestService; import org.slf4j.Logger; @@ -53,7 +51,7 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader try { // Attempt to find WebSocket servlet - GuacamoleClassLoader.getInstance().findClass("javax.websocket.Endpoint"); + Class.forName("javax.websocket.Endpoint"); // Support found return true; @@ -65,12 +63,6 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader catch (ClassNotFoundException e) {} catch (NoClassDefFoundError e) {} - // Log all GuacamoleExceptions - catch (GuacamoleException e) { - logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); - logger.debug("Error loading/detecting WebSocket support.", e); - } - // Support not found return false; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java index 8bf0a4a42..9e94da9b2 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java @@ -23,8 +23,6 @@ package org.glyptodon.guacamole.net.basic.websocket.jetty8; import com.google.inject.servlet.ServletModule; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; import org.glyptodon.guacamole.net.basic.TunnelLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +45,7 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader try { // Attempt to find WebSocket servlet - GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet"); + Class.forName("org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet"); // Support found return true; @@ -59,12 +57,6 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader catch (ClassNotFoundException e) {} catch (NoClassDefFoundError e) {} - // Log all GuacamoleExceptions - catch (GuacamoleException e) { - logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); - logger.debug("Error loading/detecting WebSocket support.", e); - } - // Support not found return false; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java index 6a67eae49..de27e0213 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java @@ -23,8 +23,6 @@ package org.glyptodon.guacamole.net.basic.websocket.jetty9; import com.google.inject.servlet.ServletModule; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; import org.glyptodon.guacamole.net.basic.TunnelLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +45,7 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader try { // Attempt to find WebSocket servlet - GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet"); + Class.forName("org.glyptodon.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet"); // Support found return true; @@ -59,12 +57,6 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader catch (ClassNotFoundException e) {} catch (NoClassDefFoundError e) {} - // Log all GuacamoleExceptions - catch (GuacamoleException e) { - logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); - logger.debug("Error loading/detecting WebSocket support.", e); - } - // Support not found return false; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java index 4c59b78c5..432818601 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java @@ -23,8 +23,6 @@ package org.glyptodon.guacamole.net.basic.websocket.tomcat; import com.google.inject.servlet.ServletModule; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; import org.glyptodon.guacamole.net.basic.TunnelLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +45,7 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader try { // Attempt to find WebSocket servlet - GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet"); + Class.forName("org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet"); // Support found return true; @@ -59,12 +57,6 @@ public class WebSocketTunnelModule extends ServletModule implements TunnelLoader catch (ClassNotFoundException e) {} catch (NoClassDefFoundError e) {} - // Log all GuacamoleExceptions - catch (GuacamoleException e) { - logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); - logger.debug("Error loading/detecting WebSocket support.", e); - } - // Support not found return false; From 3bcfea76f2ef18affa473e1d24fcac08ba9bb702 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 11:12:20 -0700 Subject: [PATCH 07/22] GUAC-587: Use GUACAMOLE_HOME/lib as lib directory. --- .../basic/extension/DirectoryClassLoader.java | 156 ++++++++++++++++++ .../net/basic/extension/ExtensionModule.java | 37 ++++- 2 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java new file mode 100644 index 000000000..ad9f8c6dc --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.extension; + +import java.io.File; +import java.io.FilenameFilter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collection; +import org.glyptodon.guacamole.GuacamoleException; + +/** + * A ClassLoader implementation which finds classes within .jar files within a + * given directory. + * + * @author Michael Jumper + */ +public class DirectoryClassLoader extends URLClassLoader { + + /** + * Returns an instance of DirectoryClassLoader configured to load .jar + * files from the given directory. Calling this function multiple times + * will not affect previously-returned instances of DirectoryClassLoader. + * + * @param dir + * The directory from which .jar files should be read. + * + * @return + * A DirectoryClassLoader instance which loads classes from the .jar + * files in the given directory. + * + * @throws GuacamoleException + * If the given file is not a directory, or the contents of the given + * directory cannot be read. + */ + public static DirectoryClassLoader getInstance(final File dir) + throws GuacamoleException { + + try { + // Attempt to create singleton classloader which loads classes from + // all .jar's in the lib directory defined in guacamole.properties + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + + @Override + public DirectoryClassLoader run() throws GuacamoleException { + return new DirectoryClassLoader(dir); + } + + }); + } + + catch (PrivilegedActionException e) { + throw (GuacamoleException) e.getException(); + } + + } + + /** + * Returns all .jar files within the given directory as an array of URLs. + * + * @param dir + * The directory to retrieve all .jar files from + * + * @return + * An array of the URLs of all .jar files within the given directory. + * + * @throws GuacamoleException + * If the given file is not a directory, or the contents of the given + * directory cannot be read. + */ + private static URL[] getJarURLs(File dir) throws GuacamoleException { + + // Validate directory is indeed a directory + if (!dir.isDirectory()) + throw new GuacamoleException(dir + " is not a directory."); + + // Get list of URLs for all .jar's in the lib directory + Collection jarURLs = new ArrayList(); + File[] files = dir.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + + // If it ends with .jar, accept the file + return name.endsWith(".jar"); + + } + + }); + + // Verify directory was successfully read + if (files == null) + throw new GuacamoleException("Unable to read contents of directory " + dir); + + // Add the URL for each .jar to the jar URL list + for (File file : files) { + + try { + jarURLs.add(file.toURI().toURL()); + } + catch (MalformedURLException e) { + throw new GuacamoleException(e); + } + + } + + // Set delegate classloader to new URLClassLoader which loads from the + // .jars found above. + + URL[] urls = new URL[jarURLs.size()]; + return jarURLs.toArray(urls); + + } + + /** + * Creates a new DirectoryClassLoader configured to load .jar files from + * the given directory. + * + * @param dir + * The directory from which .jar files should be read. + * + * @throws GuacamoleException + * If the given file is not a directory, or the contents of the given + * directory cannot be read. + */ + + private DirectoryClassLoader(File dir) throws GuacamoleException { + super(getJarURLs(dir), DirectoryClassLoader.class.getClassLoader()); + } + +} 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 16aa55979..c2defbafb 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 @@ -51,6 +51,12 @@ public class ExtensionModule extends ServletModule { */ private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class); + /** + * The name of the directory within GUACAMOLE_HOME containing any .jars + * which should be included in the classpath of all extensions. + */ + private static final String LIB_DIRECTORY = "lib"; + /** * The name of the directory within GUACAMOLE_HOME containing all * extensions. @@ -68,6 +74,34 @@ public class ExtensionModule extends ServletModule { */ private final Environment environment; + /** + * Returns the classloader that should be used as the parent classloader + * for all extensions. If the GUACAMOLE_HOME/lib directory exists, this + * will be a classloader that loads classes from within the .jar files in + * that directory. Lacking the GUACAMOLE_HOME/lib directory, this will + * simply be the classloader associated with the ExtensionModule class. + * + * @return + * The classloader that should be used as the parent classloader for + * all extensions. + * + * @throws GuacamoleException + * If an error occurs while retrieving the classloader. + */ + private ClassLoader getParentClassLoader() throws GuacamoleException { + + // Retrieve lib directory + File libDir = new File(environment.getGuacamoleHome(), LIB_DIRECTORY); + + // If lib directory does not exist, use default class loader + if (!libDir.isDirectory()) + return ExtensionModule.class.getClassLoader(); + + // Return classloader which loads classes from all .jars within the lib directory + return DirectoryClassLoader.getInstance(libDir); + + } + /** * Creates a module which loads all extensions within the * GUACAMOLE_HOME/extensions directory. @@ -112,9 +146,8 @@ public class ExtensionModule extends ServletModule { try { - // FIXME: Use class loader which reads from the lib directory // Load extension from file - Extension extension = new Extension(ExtensionModule.class.getClassLoader(), extensionFile); + Extension extension = new Extension(getParentClassLoader(), extensionFile); // Add any JavaScript / CSS resources javaScriptResources.addAll(extension.getJavaScriptResources()); From b1b62aa2b4f8fd050a6cebb6f29eb970146dc0f1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 11:14:02 -0700 Subject: [PATCH 08/22] GUAC-587: Warn about "lib-directory" deprecation. --- .../org/glyptodon/guacamole/net/basic/rest/RESTAuthModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b6e5b3aee..68bc669a4 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 @@ -90,7 +90,7 @@ public class RESTAuthModule extends AbstractModule { // 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."); + logger.warn("The \"auth-provider\" and \"lib-directory\" properties are now deprecated. Please use the \"extensions\" and \"lib\" directories within GUACAMOLE_HOME instead."); bind(AuthenticationProvider.class).toInstance(authProvider); } From 3d924873f139c84895961a63edcc0ce4cab4939b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 14:24:56 -0700 Subject: [PATCH 09/22] GUAC-587: Track and load authentication providers only within ExtensionModule. Default to basic auth. Explicitly deprecate. --- .../basic/BasicServletContextListener.java | 2 +- .../net/basic/GuacamoleClassLoader.java | 5 +- .../net/basic/extension/ExtensionModule.java | 94 ++++++++++++++++++- .../AuthenticationProviderProperty.java | 39 +++----- .../properties/BasicGuacamoleProperties.java | 14 ++- .../properties/EventListenersProperty.java | 5 +- .../net/basic/rest/RESTAuthModule.java | 32 +------ 7 files changed, 123 insertions(+), 68 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java index e6f342f2c..60eeffdbb 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/BasicServletContextListener.java @@ -85,7 +85,7 @@ public class BasicServletContextListener extends GuiceServletContextListener { new EnvironmentModule(environment), new LogModule(environment), new ExtensionModule(environment), - new RESTAuthModule(environment, sessionMap), + new RESTAuthModule(sessionMap), new RESTServletModule(), new TunnelModule() ); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleClassLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleClassLoader.java index 1dcb37048..8c3f40c94 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleClassLoader.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleClassLoader.java @@ -39,10 +39,13 @@ import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; /** * A ClassLoader implementation which finds classes within a configurable - * directory. This directory is set within guacamole.properties. + * directory. This directory is set within guacamole.properties. This class + * is deprecated in favor of DirectoryClassLoader, which is automatically + * configured based on the presence/absence of GUACAMOLE_HOME/lib. * * @author Michael Jumper */ +@Deprecated public class GuacamoleClassLoader extends ClassLoader { /** 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 c2defbafb..e7cbf13c0 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 @@ -28,9 +28,11 @@ import java.io.File; import java.io.FileFilter; import java.util.ArrayList; import java.util.Collection; +import net.sourceforge.guacamole.net.basic.BasicFileAuthenticationProvider; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.net.auth.AuthenticationProvider; +import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; import org.glyptodon.guacamole.net.basic.resource.Resource; import org.glyptodon.guacamole.net.basic.resource.ResourceServlet; import org.glyptodon.guacamole.net.basic.resource.SequenceResource; @@ -74,6 +76,12 @@ public class ExtensionModule extends ServletModule { */ private final Environment environment; + /** + * The currently-bound authentication provider, if any. At the moment, we + * only support one authentication provider loaded at any one time. + */ + private Class boundAuthenticationProvider = null; + /** * Returns the classloader that should be used as the parent classloader * for all extensions. If the GUACAMOLE_HOME/lib directory exists, this @@ -113,9 +121,81 @@ public class ExtensionModule extends ServletModule { this.environment = environment; } + /** + * Reads the value of the now-deprecated "auth-provider" property from + * guacamole.properties, returning the corresponding AuthenticationProvider + * class. If no authentication provider could be read, or the property is + * not present, null is returned. + * + * As this property is deprecated, this function will also log warning + * messages if the property is actually specified. + * + * @return + * The value of the deprecated "auth-provider" property, or null if the + * property is not present. + */ + @SuppressWarnings("deprecation") // We must continue to use this property until it is truly no longer supported + private Class getAuthProviderProperty() { + + // Get and bind auth provider instance, if defined via property + try { + + // Use "auth-provider" property if present, but warn about deprecation + Class authenticationProvider = environment.getProperty(BasicGuacamoleProperties.AUTH_PROVIDER); + if (authenticationProvider != null) + logger.warn("The \"auth-provider\" and \"lib-directory\" properties are now deprecated. Please use the \"extensions\" and \"lib\" directories within GUACAMOLE_HOME instead."); + + return authenticationProvider; + + } + catch (GuacamoleException e) { + 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); + } + + return null; + + } + + /** + * Binds the given AuthenticationProvider class such that any service + * requiring access to the AuthenticationProvider can obtain it via + * injection. + * + * @param authenticationProvider + * The AuthenticationProvider class to bind. + */ + private void bindAuthenticationProvider(Class authenticationProvider) { + + // Choose auth provider for binding if not already chosen + if (boundAuthenticationProvider != null) + boundAuthenticationProvider = authenticationProvider; + + // If an auth provider is already chosen, skip and warn + else { + logger.debug("Ignoring AuthenticationProvider \"{}\".", authenticationProvider); + logger.warn("Only one authentication extension may be used at a time. Please " + + "make sure that only one authentication extension is present " + + "within the GUACAMOLE_HOME/" + EXTENSIONS_DIRECTORY + " " + + "directory, and that you are not also specifying the deprecated " + + "\"auth-provider\" property within guacamole.properties."); + return; + } + + // Bind authentication provider + logger.debug("Binding AuthenticationProvider \"{}\".", authenticationProvider); + bind(AuthenticationProvider.class).to(authenticationProvider).in(Singleton.class); + + } + @Override protected void configureServlets() { + // Load authentication provider from guacamole.properties for sake of backwards compatibility + Class authProviderProperty = getAuthProviderProperty(); + if (authProviderProperty != null) + bindAuthenticationProvider(authProviderProperty); + // Retrieve and validate extensions directory File extensionsDir = new File(environment.getGuacamoleHome(), EXTENSIONS_DIRECTORY); if (!extensionsDir.isDirectory()) @@ -153,12 +233,10 @@ public class ExtensionModule extends ServletModule { javaScriptResources.addAll(extension.getJavaScriptResources()); cssResources.addAll(extension.getCSSResources()); - // Load all authentication providers as singletons + // Attempt to load all authentication providers Collection> authenticationProviders = extension.getAuthenticationProviderClasses(); - for (Class authenticationProvider : authenticationProviders) { - logger.debug("Binding AuthenticationProvider \"{}\".", authenticationProvider); - bind(AuthenticationProvider.class).to(authenticationProvider).in(Singleton.class); - } + for (Class authenticationProvider : authenticationProviders) + bindAuthenticationProvider(authenticationProvider); // Log successful loading of extension by name logger.info("Extension \"{}\" loaded.", extension.getName()); @@ -171,6 +249,12 @@ public class ExtensionModule extends ServletModule { } + // Default to basic auth if nothing else chosen/provided + if (boundAuthenticationProvider == null) { + logger.info("Using default, \"basic\", XML-driven authentication."); + bindAuthenticationProvider(BasicFileAuthenticationProvider.class); + } + // Dynamically generate app.js and app.css from extensions serve("/app.js").with(new ResourceServlet(new SequenceResource(javaScriptResources))); serve("/app.css").with(new ResourceServlet(new SequenceResource(cssResources))); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java index dd633ed29..1714bb1e0 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,6 @@ package org.glyptodon.guacamole.net.basic.properties; -import java.lang.reflect.InvocationTargetException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; @@ -30,14 +29,18 @@ import org.glyptodon.guacamole.properties.GuacamoleProperty; /** * A GuacamoleProperty whose value is the name of a class to use to - * authenticate users. This class must implement AuthenticationProvider. + * authenticate users. This class must implement AuthenticationProvider. Use + * of this property type is deprecated in favor of the + * GUACAMOLE_HOME/extensions directory. * * @author Michael Jumper */ -public abstract class AuthenticationProviderProperty implements GuacamoleProperty { +@Deprecated +public abstract class AuthenticationProviderProperty implements GuacamoleProperty> { @Override - public AuthenticationProvider parseValue(String authProviderClassName) throws GuacamoleException { + @SuppressWarnings("unchecked") // Explicitly checked within by isAssignableFrom() + public Class parseValue(String authProviderClassName) throws GuacamoleException { // If no property provided, return null. if (authProviderClassName == null) @@ -46,35 +49,21 @@ public abstract class AuthenticationProviderProperty implements GuacamolePropert // Get auth provider instance try { - Object obj = GuacamoleClassLoader.getInstance().loadClass(authProviderClassName) - .getConstructor().newInstance(); + // Get authentication provider class + Class authProviderClass = GuacamoleClassLoader.getInstance().loadClass(authProviderClassName); - if (!(obj instanceof AuthenticationProvider)) + // Verify the located class is actually a subclass of AuthenticationProvider + if (!AuthenticationProvider.class.isAssignableFrom(authProviderClass)) throw new GuacamoleException("Specified authentication provider class is not a AuthenticationProvider."); - return (AuthenticationProvider) obj; + // Return located class + return (Class) authProviderClass; } catch (ClassNotFoundException e) { throw new GuacamoleException("Authentication provider class not found", e); } - catch (NoSuchMethodException e) { - throw new GuacamoleException("Default constructor for authentication provider not present", e); - } - catch (SecurityException e) { - throw new GuacamoleException("Creation of authentication provider disallowed; check your security settings", e); - } - catch (InstantiationException e) { - throw new GuacamoleException("Unable to instantiate authentication provider", e); - } - catch (IllegalAccessException e) { - throw new GuacamoleException("Unable to access default constructor of authentication provider", e); - } - catch (InvocationTargetException e) { - throw new GuacamoleException("Internal error in constructor of authentication provider", e.getTargetException()); - } } } - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java index f2de45e3b..05bcb8d49 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java @@ -22,7 +22,6 @@ package org.glyptodon.guacamole.net.basic.properties; -import org.glyptodon.guacamole.properties.BooleanGuacamoleProperty; import org.glyptodon.guacamole.properties.FileGuacamoleProperty; import org.glyptodon.guacamole.properties.IntegerGuacamoleProperty; @@ -40,8 +39,10 @@ public class BasicGuacamoleProperties { /** * The authentication provider to user when retrieving the authorized - * configurations of a user. + * configurations of a user. This property is currently supported, but + * deprecated in favor of the GUACAMOLE_HOME/extensions directory. */ + @Deprecated public static final AuthenticationProviderProperty AUTH_PROVIDER = new AuthenticationProviderProperty() { @Override @@ -50,8 +51,11 @@ public class BasicGuacamoleProperties { }; /** - * The directory to search for authentication provider classes. + * The directory to search for authentication provider classes. This + * property is currently supported, but deprecated in favor of the + * GUACAMOLE_HOME/lib directory. */ + @Deprecated public static final FileGuacamoleProperty LIB_DIRECTORY = new FileGuacamoleProperty() { @Override @@ -60,7 +64,9 @@ public class BasicGuacamoleProperties { }; /** - * The comma-separated list of all classes to use as event listeners. + * The comma-separated list of all classes to use as event listeners. This + * property is currently supported, but deprecated in favor of declared + * event listeners within extension manifests. */ public static final EventListenersProperty EVENT_LISTENERS = new EventListenersProperty() { diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java index 5b7e9a4b5..0046faa1d 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java @@ -30,10 +30,13 @@ import org.glyptodon.guacamole.properties.GuacamoleProperty; /** * A GuacamoleProperty whose value is a comma-separated list of class names, - * where each class will be used as a listener for events. + * where each class will be used as a listener for events. This type of + * property is deprecated in favor of declaring event listeners within + * extension manifests. * * @author Michael Jumper */ +@SuppressWarnings("deprecation") public abstract class EventListenersProperty implements GuacamoleProperty> { @Override 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 68bc669a4..ddc5fff78 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 @@ -23,10 +23,6 @@ package org.glyptodon.guacamole.net.basic.rest; import com.google.inject.AbstractModule; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.environment.Environment; -import org.glyptodon.guacamole.net.auth.AuthenticationProvider; -import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; import org.glyptodon.guacamole.net.basic.rest.auth.AuthTokenGenerator; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.glyptodon.guacamole.net.basic.rest.auth.SecureRandomAuthTokenGenerator; @@ -47,11 +43,6 @@ public class RESTAuthModule extends AbstractModule { */ private final Logger logger = LoggerFactory.getLogger(RESTAuthModule.class); - /** - * The Guacamole server environment. - */ - private final Environment environment; - /** * Singleton instance of TokenSessionMap. */ @@ -61,16 +52,11 @@ public class RESTAuthModule extends AbstractModule { * Creates a module which handles binding of authentication-related * objects, including the singleton TokenSessionMap. * - * @param environment - * The environment to use when configuring authentication. - * * @param tokenSessionMap * An instance of TokenSessionMap to inject as a singleton wherever * needed. */ - public RESTAuthModule(Environment environment, - TokenSessionMap tokenSessionMap) { - this.environment = environment; + public RESTAuthModule(TokenSessionMap tokenSessionMap) { this.tokenSessionMap = tokenSessionMap; } @@ -84,22 +70,6 @@ public class RESTAuthModule extends AbstractModule { bind(AuthenticationService.class); bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class); - // Get and bind auth provider instance, if defined via property - try { - - // 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\" and \"lib-directory\" properties are now deprecated. Please use the \"extensions\" and \"lib\" directories within GUACAMOLE_HOME instead."); - bind(AuthenticationProvider.class).toInstance(authProvider); - } - - } - catch (GuacamoleException e) { - 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); - } - } } From 8cbad4e1510820e642a6ab8f0881ca553f2593d8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 14:43:33 -0700 Subject: [PATCH 10/22] GUAC-587: Remove ancient eventing system - it's been partially broken since 0.9.4, and really should be replaced with a true eventing subsystem. --- .../guacamole/net/basic/GuacamoleSession.java | 61 ------------ .../net/basic/TunnelRequestService.java | 92 ------------------- .../properties/BasicGuacamoleProperties.java | 12 --- .../properties/EventListenersProperty.java | 75 --------------- 4 files changed, 240 deletions(-) delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java index ed2223436..799907b39 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java @@ -60,11 +60,6 @@ public class GuacamoleSession { */ private UserContext userContext; - /** - * Collection of all event listeners configured in guacamole.properties. - */ - private final Collection listeners = new ArrayList(); - /** * The current clipboard state. */ @@ -98,52 +93,9 @@ public class GuacamoleSession { */ public GuacamoleSession(Environment environment, Credentials credentials, UserContext userContext) throws GuacamoleException { - this.lastAccessedTime = System.currentTimeMillis(); this.credentials = credentials; this.userContext = userContext; - - // Load listeners from guacamole.properties - try { - - // Get all listener classes from properties - Collection listenerClasses = - environment.getProperty(BasicGuacamoleProperties.EVENT_LISTENERS); - - // Add an instance of each class to the list - if (listenerClasses != null) { - for (Class listenerClass : listenerClasses) { - - // Instantiate listener - Object listener = listenerClass.getConstructor().newInstance(); - - // Add listener to collection of listeners - listeners.add(listener); - - } - } - - } - catch (InstantiationException e) { - throw new GuacamoleException("Listener class is abstract.", e); - } - catch (IllegalAccessException e) { - throw new GuacamoleException("No access to listener constructor.", e); - } - catch (IllegalArgumentException e) { - // This should not happen, given there ARE no arguments - throw new GuacamoleException("Illegal arguments to listener constructor.", e); - } - catch (InvocationTargetException e) { - throw new GuacamoleException("Error while instantiating listener.", e); - } - catch (NoSuchMethodException e) { - throw new GuacamoleException("Listener has no default constructor.", e); - } - catch (SecurityException e) { - throw new GuacamoleException("Security restrictions prevent instantiation of listener.", e); - } - } /** @@ -198,19 +150,6 @@ public class GuacamoleSession { return clipboardState; } - /** - * Returns a collection which iterates over instances of all listeners - * defined in guacamole.properties. For each listener defined in - * guacamole.properties, a new instance is created and stored in this - * collection. - * - * @return A collection which iterates over instances of all listeners - * defined in guacamole.properties. - */ - public Collection getListeners() { - return Collections.unmodifiableCollection(listeners); - } - /** * Returns whether this session has any associated active tunnels. * diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java index 3f312160a..cee5cecc1 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java @@ -38,10 +38,6 @@ import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; -import org.glyptodon.guacamole.net.event.TunnelCloseEvent; -import org.glyptodon.guacamole.net.event.TunnelConnectEvent; -import org.glyptodon.guacamole.net.event.listener.TunnelCloseListener; -import org.glyptodon.guacamole.net.event.listener.TunnelConnectListener; import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,84 +72,6 @@ public class TunnelRequestService { @Inject private AuthenticationService authenticationService; - /** - * Notifies all listeners in the given session that a tunnel has been - * connected. - * - * @param session The session associated with the listeners to be notified. - * @param tunnel The tunnel being connected. - * @return true if all listeners are allowing the tunnel to connect, - * or if there are no listeners, and false if any listener is - * canceling the connection. Note that once one listener cancels, - * no other listeners will run. - * @throws GuacamoleException If any listener throws an error while being - * notified. Note that if any listener throws an - * error, the connect is canceled, and no other - * listeners will run. - */ - private boolean notifyConnect(GuacamoleSession session, GuacamoleTunnel tunnel) - throws GuacamoleException { - - // Build event for auth success - TunnelConnectEvent event = new TunnelConnectEvent( - session.getUserContext(), - session.getCredentials(), - tunnel); - - // Notify all listeners - for (Object listener : session.getListeners()) { - if (listener instanceof TunnelConnectListener) { - - // Cancel immediately if hook returns false - if (!((TunnelConnectListener) listener).tunnelConnected(event)) - return false; - - } - } - - return true; - - } - - /** - * Notifies all listeners in the given session that a tunnel has been - * closed. - * - * @param session The session associated with the listeners to be notified. - * @param tunnel The tunnel being closed. - * @return true if all listeners are allowing the tunnel to close, - * or if there are no listeners, and false if any listener is - * canceling the close. Note that once one listener cancels, - * no other listeners will run. - * @throws GuacamoleException If any listener throws an error while being - * notified. Note that if any listener throws an - * error, the close is canceled, and no other - * listeners will run. - */ - private boolean notifyClose(GuacamoleSession session, GuacamoleTunnel tunnel) - throws GuacamoleException { - - // Build event for auth success - TunnelCloseEvent event = new TunnelCloseEvent( - session.getUserContext(), - session.getCredentials(), - tunnel); - - // Notify all listeners - for (Object listener : session.getListeners()) { - if (listener instanceof TunnelCloseListener) { - - // Cancel immediately if hook returns false - if (!((TunnelCloseListener) listener).tunnelClosed(event)) - return false; - - } - } - - return true; - - } - /** * Reads and returns the client information provided within the given * request. @@ -335,10 +253,6 @@ public class TunnelRequestService { @Override public void close() throws GuacamoleException { - // Signal listeners - if (!notifyClose(session, this)) - throw new GuacamoleException("Tunnel close canceled by listener."); - session.removeTunnel(getUUID().toString()); // Close if no exception due to listener @@ -348,12 +262,6 @@ public class TunnelRequestService { }; - // Notify listeners about connection - if (!notifyConnect(session, monitoredTunnel)) { - logger.info("Successful connection canceled by hook."); - return null; - } - // Associate tunnel with session session.addTunnel(monitoredTunnel); return monitoredTunnel; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java index 05bcb8d49..a1b2e1336 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/BasicGuacamoleProperties.java @@ -63,18 +63,6 @@ public class BasicGuacamoleProperties { }; - /** - * The comma-separated list of all classes to use as event listeners. This - * property is currently supported, but deprecated in favor of declared - * event listeners within extension manifests. - */ - public static final EventListenersProperty EVENT_LISTENERS = new EventListenersProperty() { - - @Override - public String getName() { return "event-listeners"; } - - }; - /** * The session timeout for the API, in minutes. */ diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java deleted file mode 100644 index 0046faa1d..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/EventListenersProperty.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.properties; - -import java.util.ArrayList; -import java.util.Collection; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; -import org.glyptodon.guacamole.properties.GuacamoleProperty; - -/** - * A GuacamoleProperty whose value is a comma-separated list of class names, - * where each class will be used as a listener for events. This type of - * property is deprecated in favor of declaring event listeners within - * extension manifests. - * - * @author Michael Jumper - */ -@SuppressWarnings("deprecation") -public abstract class EventListenersProperty implements GuacamoleProperty> { - - @Override - public Collection parseValue(String classNameList) throws GuacamoleException { - - // If no property provided, return null. - if (classNameList == null) - return null; - - // Parse list - String[] classNames = classNameList.split(",[\\s]*"); - - // Fill list of classes - Collection listeners = new ArrayList(); - try { - - // Load all classes in list - for (String className : classNames) { - Class clazz = GuacamoleClassLoader.getInstance().loadClass(className); - listeners.add(clazz); - } - - } - catch (ClassNotFoundException e) { - throw new GuacamoleException("Listener class not found.", e); - } - catch (SecurityException e) { - throw new GuacamoleException("Security settings prevent loading of listener class.", e); - } - - return listeners; - - } - -} - From 7352ce12ea55fa2f67552bace084b5003c401323 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 15:24:14 -0700 Subject: [PATCH 11/22] GUAC-587: Do not import deprecated GuacamoleClassloader. Not all Java compilers are happy with this import, even though the importing class is also deprecated. --- .../net/basic/properties/AuthenticationProviderProperty.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java index 1714bb1e0..c2dd97d04 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/properties/AuthenticationProviderProperty.java @@ -24,7 +24,6 @@ package org.glyptodon.guacamole.net.basic.properties; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.AuthenticationProvider; -import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; import org.glyptodon.guacamole.properties.GuacamoleProperty; /** @@ -50,7 +49,7 @@ public abstract class AuthenticationProviderProperty implements GuacamolePropert try { // Get authentication provider class - Class authProviderClass = GuacamoleClassLoader.getInstance().loadClass(authProviderClassName); + Class authProviderClass = org.glyptodon.guacamole.net.basic.GuacamoleClassLoader.getInstance().loadClass(authProviderClassName); // Verify the located class is actually a subclass of AuthenticationProvider if (!AuthenticationProvider.class.isAssignableFrom(authProviderClass)) From 502684157335cb476d23ce7bdddb8a42506a8a60 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 15:32:21 -0700 Subject: [PATCH 12/22] GUAC-587: Add guac-manifest.json to noauth. --- .../net/auth/noauth/NoAuthenticationProvider.java | 1 - .../src/main/resources/guac-manifest.json | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json diff --git a/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java b/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java index 7b0adfafb..f655a5fc3 100644 --- a/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java +++ b/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java @@ -54,7 +54,6 @@ import org.xml.sax.helpers.XMLReaderFactory; * * Example `guacamole.properties`: * - * auth-provider: net.sourceforge.guacamole.net.auth.noauth.NoAuthenticationProvider * noauth-config: /etc/guacamole/noauth-config.xml * * diff --git a/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..588855a70 --- /dev/null +++ b/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json @@ -0,0 +1,10 @@ +{ + + "name" : "Disabled Authentication", + "namespace" : "guac-noauth", + + "authProviders" : [ + "net.sourceforge.guacamole.net.auth.noauth.NoAuthenticationProvider" + ] + +} From acc8d996fcdf6c736bd8d1c7b91dd5b6b79c41df Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 15:34:02 -0700 Subject: [PATCH 13/22] GUAC-587: Add guac-manifest.json to LDAP. --- .../src/main/resources/guac-manifest.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json diff --git a/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..b2633a170 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json @@ -0,0 +1,10 @@ +{ + + "name" : "LDAP Authentication", + "namespace" : "guac-ldap", + + "authProviders" : [ + "net.sourceforge.guacamole.net.auth.ldap.LDAPAuthenticationProvider" + ] + +} From 3411c1b45ae19086bf4d409f4745f1370db5f970 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 15:52:36 -0700 Subject: [PATCH 14/22] GUAC-587: Migrate noauth and LDAP to self-contained .jar files. --- extensions/guacamole-auth-ldap/pom.xml | 22 ++++++++ .../src/main/assembly/dist.xml | 52 +++++++------------ extensions/guacamole-auth-noauth/pom.xml | 22 ++++++++ .../src/main/assembly/dist.xml | 34 ++++-------- 4 files changed, 73 insertions(+), 57 deletions(-) diff --git a/extensions/guacamole-auth-ldap/pom.xml b/extensions/guacamole-auth-ldap/pom.xml index 8e67ab405..340b5f4d9 100644 --- a/extensions/guacamole-auth-ldap/pom.xml +++ b/extensions/guacamole-auth-ldap/pom.xml @@ -32,6 +32,26 @@ + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + unpack-dependencies + prepare-package + + unpack-dependencies + + + runtime + ${project.build.directory}/classes + + + + + maven-assembly-plugin @@ -64,6 +84,7 @@ org.glyptodon.guacamole guacamole-common 0.9.6 + provided @@ -71,6 +92,7 @@ org.glyptodon.guacamole guacamole-ext 0.9.6 + provided diff --git a/extensions/guacamole-auth-ldap/src/main/assembly/dist.xml b/extensions/guacamole-auth-ldap/src/main/assembly/dist.xml index 9b0b27dde..59a416b70 100644 --- a/extensions/guacamole-auth-ldap/src/main/assembly/dist.xml +++ b/extensions/guacamole-auth-ldap/src/main/assembly/dist.xml @@ -11,43 +11,29 @@ tar.gz - + - - - doc - + + + doc + - - - schema - schema - + + + schema + schema + + + + + target + + + *.jar + + - - - - - lib - runtime - false - true - true - - - - - org.glyptodon.guacamole:guacamole-common - - - org.glyptodon.guacamole:guacamole-ext - - - - - diff --git a/extensions/guacamole-auth-noauth/pom.xml b/extensions/guacamole-auth-noauth/pom.xml index 4596d32d6..67899f164 100644 --- a/extensions/guacamole-auth-noauth/pom.xml +++ b/extensions/guacamole-auth-noauth/pom.xml @@ -32,6 +32,26 @@ + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + unpack-dependencies + prepare-package + + unpack-dependencies + + + runtime + ${project.build.directory}/classes + + + + + maven-assembly-plugin @@ -64,6 +84,7 @@ org.glyptodon.guacamole guacamole-common 0.9.6 + provided @@ -71,6 +92,7 @@ org.glyptodon.guacamole guacamole-ext 0.9.6 + provided diff --git a/extensions/guacamole-auth-noauth/src/main/assembly/dist.xml b/extensions/guacamole-auth-noauth/src/main/assembly/dist.xml index 610749432..834d5beff 100644 --- a/extensions/guacamole-auth-noauth/src/main/assembly/dist.xml +++ b/extensions/guacamole-auth-noauth/src/main/assembly/dist.xml @@ -11,7 +11,7 @@ tar.gz - + @@ -19,29 +19,15 @@ doc + + + target + + + *.jar + + + - - - - - lib - runtime - false - true - true - - - - - org.glyptodon.guacamole:guacamole-common - - - org.glyptodon.guacamole:guacamole-ext - - - - - From ab5fa56577b1a214c9f74230421c4073ffebe661 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 15:53:00 -0700 Subject: [PATCH 15/22] GUAC-587: Include only runtime dependencies within JDBC auth .jar files. --- .../modules/guacamole-auth-jdbc-mysql/pom.xml | 1 + .../modules/guacamole-auth-jdbc-postgresql/pom.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/pom.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/pom.xml index 436cb6ca3..b0d898f95 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/pom.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/pom.xml @@ -51,6 +51,7 @@ unpack-dependencies + runtime ${project.build.directory}/classes diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/pom.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/pom.xml index 207348690..85db10341 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/pom.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/pom.xml @@ -51,6 +51,7 @@ unpack-dependencies + runtime ${project.build.directory}/classes From d0d492faec9f16cc07919f2f9e40808d06676453 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 May 2015 16:19:55 -0700 Subject: [PATCH 16/22] GUAC-587: Fix flipped logic within bindAuthenticationProvider(). --- .../guacamole/net/basic/extension/ExtensionModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e7cbf13c0..84a4484d7 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 @@ -168,7 +168,7 @@ public class ExtensionModule extends ServletModule { private void bindAuthenticationProvider(Class authenticationProvider) { // Choose auth provider for binding if not already chosen - if (boundAuthenticationProvider != null) + if (boundAuthenticationProvider == null) boundAuthenticationProvider = authenticationProvider; // If an auth provider is already chosen, skip and warn From abd3d381f939a6bb76496583c4eeb239be5ebf26 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 12 May 2015 13:31:18 -0700 Subject: [PATCH 17/22] GUAC-587: Include and validate Guacamole version in manifest. --- .../tunnel/ManagedSSLGuacamoleSocket.java | 71 +++++++++++++++++++ .../src/main/resources/guac-manifest.json | 2 + .../src/main/resources/guac-manifest.json | 2 + .../src/main/resources/guac-manifest.json | 2 + .../src/main/resources/guac-manifest.json | 2 + .../net/basic/extension/Extension.java | 12 ++++ .../basic/extension/ExtensionManifest.java | 31 ++++++++ .../net/basic/extension/ExtensionModule.java | 36 ++++++++++ 8 files changed, 158 insertions(+) create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java new file mode 100644 index 000000000..739b4776e --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.jdbc.tunnel; + +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.InetGuacamoleSocket; + +/** + * Implementation of GuacamoleSocket which connects via TCP to a given hostname + * and port. If the socket is closed for any reason, a given task is run. + * + * @author Michael Jumper + */ +public class ManagedInetGuacamoleSocket extends InetGuacamoleSocket { + + /** + * The task to run when the socket is closed. + */ + private final Runnable socketClosedTask; + + /** + * Creates a new socket which connects via TCP to a given hostname and + * port. If the socket is closed for any reason, the given task is run. + * + * @param hostname + * The hostname of the Guacamole proxy server to connect to. + * + * @param port + * The port of the Guacamole proxy server to connect to. + * + * @param socketClosedTask + * The task to run when the socket is closed. This task will NOT be + * run if an exception occurs during connection, and this + * ManagedInetGuacamoleSocket instance is ultimately not created. + * + * @throws GuacamoleException + * If an error occurs while connecting to the Guacamole proxy server. + */ + public ManagedInetGuacamoleSocket(String hostname, int port, + Runnable socketClosedTask) throws GuacamoleException { + super(hostname, port); + this.socketClosedTask = socketClosedTask; + } + + @Override + public void close() throws GuacamoleException { + super.close(); + socketClosedTask.run(); + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json index d9aad44f3..3c7deb865 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/guac-manifest.json @@ -1,5 +1,7 @@ { + "guacamoleVersion" : "0.9.6", + "name" : "MySQL Authentication", "namespace" : "guac-mysql", diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json index 59b5ed7ca..2be870f25 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json @@ -1,5 +1,7 @@ { + "guacamoleVersion" : "0.9.6", + "name" : "PostgreSQL Authentication", "namespace" : "guac-postgresql", diff --git a/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json index b2633a170..e8d9a9e7b 100644 --- a/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json @@ -1,5 +1,7 @@ { + "guacamoleVersion" : "0.9.6", + "name" : "LDAP Authentication", "namespace" : "guac-ldap", diff --git a/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json index 588855a70..03fd29916 100644 --- a/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-noauth/src/main/resources/guac-manifest.json @@ -1,5 +1,7 @@ { + "guacamoleVersion" : "0.9.6", + "name" : "Disabled Authentication", "namespace" : "guac-noauth", 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 61eb16c17..2f3aa17c4 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 @@ -289,6 +289,18 @@ public class Extension { } + /** + * Returns the version of the Guacamole web application for which this + * extension was built. + * + * @return + * The version of the Guacamole web application for which this + * extension was built. + */ + public String getGuacamoleVersion() { + return manifest.getGuacamoleVersion(); + } + /** * Returns the name of this extension, as declared in the extension's * manifest. 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 c593865d0..093af3b0d 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 @@ -33,6 +33,13 @@ import org.codehaus.jackson.annotate.JsonProperty; */ public class ExtensionManifest { + /** + * The version of Guacamole for which this extension was built. + * Compatibility rules built into the web application will guard against + * incompatible extensions being loaded. + */ + private String guacamoleVersion; + /** * The name of the extension associated with this manifest. The extension * name is human-readable, and used for display purposes only. @@ -65,6 +72,30 @@ public class ExtensionManifest { */ private Collection authProviders; + /** + * Returns the version of the Guacamole web application for which the + * extension was built, such as "0.9.6". + * + * @return + * The version of the Guacamole web application for which the extension + * was built. + */ + public String getGuacamoleVersion() { + return guacamoleVersion; + } + + /** + * Sets the version of the Guacamole web application for which the + * extension was built, such as "0.9.6". + * + * @param guacamoleVersion + * The version of the Guacamole web application for which the extension + * was built. + */ + public void setGuacamoleVersion(String guacamoleVersion) { + this.guacamoleVersion = guacamoleVersion; + } + /** * Returns the name of the extension associated with this manifest. The * name is human-readable, for display purposes only, and is defined within 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 84a4484d7..e2bae9bdf 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 @@ -27,9 +27,13 @@ import com.google.inject.servlet.ServletModule; import java.io.File; import java.io.FileFilter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.List; import net.sourceforge.guacamole.net.basic.BasicFileAuthenticationProvider; import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleServerException; import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; @@ -53,6 +57,15 @@ public class ExtensionModule extends ServletModule { */ private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class); + /** + * The version strings of all Guacamole versions whose extensions are + * compatible with this release. + */ + private static final List ALLOWED_GUACAMOLE_VERSIONS = + Collections.unmodifiableList(Arrays.asList( + "0.9.6" + )); + /** * The name of the directory within GUACAMOLE_HOME containing any .jars * which should be included in the classpath of all extensions. @@ -188,6 +201,21 @@ public class ExtensionModule extends ServletModule { } + /** + * Returns whether the given version of Guacamole is compatible with this + * version of Guacamole as far as extensions are concerned. + * + * @param guacamoleVersion + * The version of Guacamole the extension was built for. + * + * @return + * true if the given version of Guacamole is compatible with this + * version of Guacamole, false otherwise. + */ + private boolean isCompatible(String guacamoleVersion) { + return ALLOWED_GUACAMOLE_VERSIONS.contains(guacamoleVersion); + } + @Override protected void configureServlets() { @@ -229,6 +257,14 @@ public class ExtensionModule extends ServletModule { // Load extension from file Extension extension = new Extension(getParentClassLoader(), extensionFile); + // Validate Guacamole version of extension + if (!isCompatible(extension.getGuacamoleVersion())) { + logger.debug("Declared Guacamole version \"{}\" of extension \"{}\" is not compatible with this version of Guacamole.", + extension.getGuacamoleVersion(), extensionFile.getName()); + throw new GuacamoleServerException("Extension \"" + extension.getName() + "\" is not " + + "compatible with this version of Guacamole."); + } + // Add any JavaScript / CSS resources javaScriptResources.addAll(extension.getJavaScriptResources()); cssResources.addAll(extension.getCSSResources()); From 5232dac8962ea56db25d34150bbc0e4f105d32cf Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 12 May 2015 13:41:40 -0700 Subject: [PATCH 18/22] GUAC-587: Default to GUACAMOLE_HOME/user-mapping.xml. --- .../basic/BasicFileAuthenticationProvider.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java index 6694f5eea..fd7ffda9f 100644 --- a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java +++ b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java @@ -76,7 +76,7 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide private final Environment environment; /** - * The filename of the XML file to read the user user_mapping from. + * The XML file to read the user mapping from. */ public static final FileGuacamoleProperty BASIC_USER_MAPPING = new FileGuacamoleProperty() { @@ -85,6 +85,12 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide }; + /** + * The default filename to use for the user mapping, if not defined within + * guacamole.properties. + */ + public static final String DEFAULT_USER_MAPPING = "user-mapping.xml"; + /** * Creates a new BasicFileAuthenticationProvider that authenticates users * against simple, monolithic XML file. @@ -110,9 +116,10 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide */ private UserMapping getUserMapping() throws GuacamoleException { - // Get user user_mapping file - File user_mapping_file = - environment.getRequiredProperty(BASIC_USER_MAPPING); + // Get user mapping file, defaulting to GUACAMOLE_HOME/user-mapping.xml + File user_mapping_file = environment.getProperty(BASIC_USER_MAPPING); + if (user_mapping_file == null) + user_mapping_file = new File(environment.getGuacamoleHome(), DEFAULT_USER_MAPPING); // If user_mapping not yet read, or user_mapping has been modified, reread if (user_mapping == null || From 190f61d92761e9c9507c9e1aa169d861c53f2ec1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 12 May 2015 13:53:32 -0700 Subject: [PATCH 19/22] GUAC-587: Default to noauth-config.xml. --- .../auth/noauth/NoAuthenticationProvider.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java b/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java index f655a5fc3..3752f8029 100644 --- a/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java +++ b/extensions/guacamole-auth-noauth/src/main/java/net/sourceforge/guacamole/net/auth/noauth/NoAuthenticationProvider.java @@ -92,7 +92,7 @@ public class NoAuthenticationProvider extends SimpleAuthenticationProvider { private final Environment environment; /** - * The filename of the XML file to read the user mapping from. + * The XML file to read the configuration from. */ public static final FileGuacamoleProperty NOAUTH_CONFIG = new FileGuacamoleProperty() { @@ -103,6 +103,12 @@ public class NoAuthenticationProvider extends SimpleAuthenticationProvider { }; + /** + * The default filename to use for the configuration, if not defined within + * guacamole.properties. + */ + public static final String DEFAULT_NOAUTH_CONFIG = "noauth-config.xml"; + /** * Creates a new NoAuthenticationProvider that does not perform any * authentication at all. All attempts to access the Guacamole system are @@ -124,7 +130,14 @@ public class NoAuthenticationProvider extends SimpleAuthenticationProvider { * property. */ private File getConfigurationFile() throws GuacamoleException { - return environment.getRequiredProperty(NOAUTH_CONFIG); + + // Get config file, defaulting to GUACAMOLE_HOME/noauth-config.xml + File configFile = environment.getProperty(NOAUTH_CONFIG); + if (configFile == null) + configFile = new File(environment.getGuacamoleHome(), DEFAULT_NOAUTH_CONFIG); + + return configFile; + } public synchronized void init() throws GuacamoleException { From 2c027e9cb21b161df970e8baebccfd645e634744 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 12 May 2015 13:59:10 -0700 Subject: [PATCH 20/22] GUAC-587: Default to localhost:4822 for guacd. Fix absence of SSL support within JDBC auth tunnels. --- .../AbstractGuacamoleTunnelService.java | 24 ++++++++++++++----- .../tunnel/ManagedSSLGuacamoleSocket.java | 10 ++++---- .../net/auth/simple/SimpleConnection.java | 16 +++++++++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java index 9a8c67235..051f6a9cb 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java @@ -98,6 +98,18 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS @Inject private ConnectionRecordMapper connectionRecordMapper; + /** + * The hostname to use when connecting to guacd if no hostname is provided + * within guacamole.properties. + */ + private static final String DEFAULT_GUACD_HOSTNAME = "localhost"; + + /** + * The port to use when connecting to guacd if no port is provided within + * guacamole.properties. + */ + private static final int DEFAULT_GUACD_PORT = 4822; + /** * All active connections through the tunnel having a given UUID. */ @@ -266,17 +278,17 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS throws GuacamoleException { // Use SSL if requested - if (environment.getProperty(Environment.GUACD_SSL, true)) - return new ManagedInetGuacamoleSocket( - environment.getRequiredProperty(Environment.GUACD_HOSTNAME), - environment.getRequiredProperty(Environment.GUACD_PORT), + if (environment.getProperty(Environment.GUACD_SSL, false)) + return new ManagedSSLGuacamoleSocket( + environment.getProperty(Environment.GUACD_HOSTNAME, DEFAULT_GUACD_HOSTNAME), + environment.getProperty(Environment.GUACD_PORT, DEFAULT_GUACD_PORT), socketClosedCallback ); // Otherwise, just use straight TCP return new ManagedInetGuacamoleSocket( - environment.getRequiredProperty(Environment.GUACD_HOSTNAME), - environment.getRequiredProperty(Environment.GUACD_PORT), + environment.getProperty(Environment.GUACD_HOSTNAME, DEFAULT_GUACD_HOSTNAME), + environment.getProperty(Environment.GUACD_PORT, DEFAULT_GUACD_PORT), socketClosedCallback ); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java index 739b4776e..cf3f3804d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ManagedSSLGuacamoleSocket.java @@ -23,15 +23,15 @@ package org.glyptodon.guacamole.auth.jdbc.tunnel; import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.InetGuacamoleSocket; +import org.glyptodon.guacamole.net.SSLGuacamoleSocket; /** - * Implementation of GuacamoleSocket which connects via TCP to a given hostname + * Implementation of GuacamoleSocket which connects via SSL to a given hostname * and port. If the socket is closed for any reason, a given task is run. * * @author Michael Jumper */ -public class ManagedInetGuacamoleSocket extends InetGuacamoleSocket { +public class ManagedSSLGuacamoleSocket extends SSLGuacamoleSocket { /** * The task to run when the socket is closed. @@ -39,7 +39,7 @@ public class ManagedInetGuacamoleSocket extends InetGuacamoleSocket { private final Runnable socketClosedTask; /** - * Creates a new socket which connects via TCP to a given hostname and + * Creates a new socket which connects via SSL to a given hostname and * port. If the socket is closed for any reason, the given task is run. * * @param hostname @@ -56,7 +56,7 @@ public class ManagedInetGuacamoleSocket extends InetGuacamoleSocket { * @throws GuacamoleException * If an error occurs while connecting to the Guacamole proxy server. */ - public ManagedInetGuacamoleSocket(String hostname, int port, + public ManagedSSLGuacamoleSocket(String hostname, int port, Runnable socketClosedTask) throws GuacamoleException { super(hostname, port); this.socketClosedTask = socketClosedTask; diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleConnection.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleConnection.java index ee3973359..e5f2f0d1e 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleConnection.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleConnection.java @@ -45,6 +45,18 @@ import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; */ public class SimpleConnection extends AbstractConnection { + /** + * The hostname to use when connecting to guacd if no hostname is provided + * within guacamole.properties. + */ + private static final String DEFAULT_GUACD_HOSTNAME = "localhost"; + + /** + * The port to use when connecting to guacd if no port is provided within + * guacamole.properties. + */ + private static final int DEFAULT_GUACD_PORT = 4822; + /** * Backing configuration, containing all sensitive information. */ @@ -92,8 +104,8 @@ public class SimpleConnection extends AbstractConnection { Environment env = new LocalEnvironment(); // Get guacd connection parameters - String hostname = env.getProperty(Environment.GUACD_HOSTNAME); - int port = env.getProperty(Environment.GUACD_PORT); + String hostname = env.getProperty(Environment.GUACD_HOSTNAME, DEFAULT_GUACD_HOSTNAME); + int port = env.getProperty(Environment.GUACD_PORT, DEFAULT_GUACD_PORT); GuacamoleSocket socket; From d066d58ee1fb2af62d01109fbe9729651edffd37 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 12 May 2015 13:59:30 -0700 Subject: [PATCH 21/22] GUAC-587: Do not require guacamole.properties. Use defaults if missing. --- .../environment/LocalEnvironment.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/environment/LocalEnvironment.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/environment/LocalEnvironment.java index 5421bceec..35200c0d1 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/environment/LocalEnvironment.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/environment/LocalEnvironment.java @@ -95,28 +95,28 @@ public class LocalEnvironment implements Environment { properties = new Properties(); try { - InputStream stream; + InputStream stream = null; // If not a directory, load from classpath - if (!guacHome.isDirectory()) { - - // Read from classpath + if (!guacHome.isDirectory()) stream = LocalEnvironment.class.getResourceAsStream("/guacamole.properties"); - if (stream == null) - throw new GuacamoleServerException( - "guacamole.properties not loaded from " + guacHome - + " (not a directory), and guacamole.properties could" - + " not be found as a resource in the classpath."); - - } // Otherwise, try to load from file - else - stream = new FileInputStream(new File(guacHome, "guacamole.properties")); + else { + File propertiesFile = new File(guacHome, "guacamole.properties"); + if (propertiesFile.exists()) + stream = new FileInputStream(propertiesFile); + } - // Load properties, always close stream - try { properties.load(stream); } - finally { stream.close(); } + // Load properties from stream, if any, always closing stream when done + if (stream != null) { + try { properties.load(stream); } + finally { stream.close(); } + } + + // Notify if we're proceeding without guacamole.properties + else + logger.info("No guacamole.properties file found within GUACAMOLE_HOME or the classpath. Using defaults."); } catch (IOException e) { From cc6002f1a1ead4aee7e73f3356ac0560f447e9f6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 14 May 2015 03:33:10 +0000 Subject: [PATCH 22/22] GUAC-587: Fix formatting of comments/javadoc in DirectoryClassLoader. --- .../guacamole/net/basic/extension/DirectoryClassLoader.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java index ad9f8c6dc..4a4a8f271 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java @@ -84,7 +84,7 @@ public class DirectoryClassLoader extends URLClassLoader { * Returns all .jar files within the given directory as an array of URLs. * * @param dir - * The directory to retrieve all .jar files from + * The directory to retrieve all .jar files from. * * @return * An array of the URLs of all .jar files within the given directory. @@ -129,9 +129,7 @@ public class DirectoryClassLoader extends URLClassLoader { } - // Set delegate classloader to new URLClassLoader which loads from the - // .jars found above. - + // Set delegate classloader to new URLClassLoader which loads from the .jars found above. URL[] urls = new URL[jarURLs.size()]; return jarURLs.toArray(urls);